kimi-code/packages/protocol/src/session.ts
7Sageer 2d76c12c63 refactor: replace session lease heartbeat protocol with kernel-held flock
Per-session flock is now held for the entire time a server hosts a
session, making the kernel the single arbiter of ownership: process
death releases the lock, so heartbeats, TTL-based stale takeover, and
pid liveness probing are no longer needed. owner.json is reduced to a
redirect hint (instance address), written atomically after acquire.

- add @moonshot-ai/kernel-file-lock (fs-ext-extra-prebuilt; flock on
  unix, LockFileEx emulation on windows)
- crossProcessLockService shrinks to try-acquire/checkHeld/release
- minidb lockfile adopts the same flock-held semantics
- delete processProbe
2026-07-22 20:11:42 +08:00

164 lines
6.1 KiB
TypeScript

import { z } from 'zod';
import {
promptPermissionModeSchema,
promptThinkingSchema,
} from './rest/prompt';
import { isoDateTimeSchema } from './time';
import { workspaceIdSchema } from './workspace';
export const sessionUsageSchema = z.object({
input_tokens: z.number().int().nonnegative(),
output_tokens: z.number().int().nonnegative(),
cache_read_tokens: z.number().int().nonnegative(),
cache_creation_tokens: z.number().int().nonnegative(),
total_cost_usd: z.number().nonnegative(),
context_tokens: z.number().int().nonnegative(),
context_limit: z.number().int().nonnegative(),
turn_count: z.number().int().nonnegative(),
});
export type SessionUsage = z.infer<typeof sessionUsageSchema>;
export function emptySessionUsage(): SessionUsage {
return {
input_tokens: 0,
output_tokens: 0,
cache_read_tokens: 0,
cache_creation_tokens: 0,
total_cost_usd: 0,
context_tokens: 0,
context_limit: 0,
turn_count: 0,
};
}
export const permissionRuleMatcherSchema = z.object({
kind: z.enum(['command_prefix', 'path_glob', 'exact_input', 'always']),
value: z.string().optional(),
});
export const permissionRuleSchema = z.object({
id: z.string().min(1),
tool_name: z.string().min(1),
matcher: permissionRuleMatcherSchema.optional(),
decision: z.literal('approved'),
created_at: isoDateTimeSchema,
created_by: z.enum(['user', 'agent']),
});
export type PermissionRule = z.infer<typeof permissionRuleSchema>;
export const sessionAgentConfigSchema = z.object({
model: z.string(),
system_prompt: z.string().optional(),
tools: z.array(z.string()).optional(),
mcp_servers: z.array(z.string()).optional(),
thinking: promptThinkingSchema.optional(),
permission_mode: promptPermissionModeSchema.optional(),
plan_mode: z.boolean().optional(),
swarm_mode: z.boolean().optional(),
goal_objective: z.string().optional(),
goal_control: z.enum(['pause', 'resume', 'cancel']).optional(),
});
export type SessionAgentConfig = z.infer<typeof sessionAgentConfigSchema>;
export const sessionAgentConfigPartialSchema = sessionAgentConfigSchema.partial();
export type SessionAgentConfigPartial = z.infer<typeof sessionAgentConfigPartialSchema>;
export const sessionMetadataSchema = z
.object({
cwd: z.string().min(1),
})
.catchall(z.unknown());
export type SessionMetadata = z.infer<typeof sessionMetadataSchema>;
export const sessionPendingInteractionSchema = z.enum(['none', 'approval', 'question']);
export type SessionPendingInteraction = z.infer<typeof sessionPendingInteractionSchema>;
/**
* Per-session holder annotation derived from the kernel lock on
* `session-leases/<id>.lock` plus its optional `.owner.json` routing metadata.
* `self` = this instance holds the lock (the session is materialized here),
* `peer` = another instance holds it (`address` is its advertised base URL),
* `none` = no process holds the lock. Purely display/redirect metadata: the
* live kernel lock is the authority and is re-checked on materialization.
*/
export const sessionOwnershipSchema = z.object({
held_by: z.enum(['self', 'peer', 'none']),
address: z.string().min(1).optional(),
});
export type SessionOwnership = z.infer<typeof sessionOwnershipSchema>;
export const sessionSchema = z.object({
id: z.string().min(1),
workspace_id: workspaceIdSchema,
title: z.string(),
created_at: isoDateTimeSchema,
updated_at: isoDateTimeSchema,
/** Any agent in the session holds an active turn or background lease.
* Replaces the derived five-value `status` enum: awaiting
* states ride the approval/question channels, and turn outcomes ride
* turn.ended — clients compose their own presentation from the facts. */
busy: z.boolean(),
/** Whether the MAIN agent currently owns an active turn. Unlike `busy`,
* this excludes background tasks and sub-agent turns. Optional for wire
* compatibility with older servers. */
main_turn_active: z.boolean().optional(),
/** Highest-priority pending human interaction, so list clients can restore
* the pre-status attention badge without subscribing to every session. */
pending_interaction: sessionPendingInteractionSchema.optional(),
/** Outcome of the MAIN agent's most recent turn, when the session is live
* and a turn has ended since activation. A fact, not a state: clients
* decide how to present it (e.g. an "aborted" tag when `!busy` and the
* reason is cancelled/failed). */
last_turn_reason: z.enum(['completed', 'cancelled', 'failed']).optional(),
archived: z.boolean().optional(),
ownership: sessionOwnershipSchema.optional(),
current_prompt_id: z.string().min(1).optional(),
/** Text of the most recent user prompt, for search/preview. Absent for empty sessions. */
last_prompt: z.string().optional(),
metadata: sessionMetadataSchema,
agent_config: sessionAgentConfigSchema,
usage: sessionUsageSchema,
permission_rules: z.array(permissionRuleSchema),
message_count: z.number().int().nonnegative(),
last_seq: z.number().int().nonnegative(),
});
export type Session = z.infer<typeof sessionSchema>;
export const sessionCreateSchema = z.object({
title: z.string().min(1).optional(),
metadata: sessionMetadataSchema.optional(),
agent_config: sessionAgentConfigPartialSchema.optional(),
workspace_id: workspaceIdSchema.optional(),
});
export type SessionCreate = z.infer<typeof sessionCreateSchema>;
export const sessionUpdateSchema = z.object({
title: z.string().min(1).optional(),
metadata: sessionMetadataSchema.partial().optional(),
agent_config: sessionAgentConfigPartialSchema.optional(),
permission_rules: z.array(permissionRuleSchema).optional(),
});
export type SessionUpdate = z.infer<typeof sessionUpdateSchema>;
export const sessionForkSchema = z.object({
title: z.string().min(1).optional(),
metadata: z.record(z.string(), z.unknown()).optional(),
});
export type SessionFork = z.infer<typeof sessionForkSchema>;
export const sessionChildCreateSchema = z.object({
title: z.string().min(1).optional(),
metadata: z.record(z.string(), z.unknown()).optional(),
});
export type SessionChildCreate = z.infer<typeof sessionChildCreateSchema>;