diff --git a/.changeset/quiet-agent-sections.md b/.changeset/quiet-agent-sections.md new file mode 100644 index 0000000000..b6d8d079c0 --- /dev/null +++ b/.changeset/quiet-agent-sections.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Allow Agent Manager sessions to move their worktree between sections or ungroup it through the `agent_manager` tool. diff --git a/packages/kilo-docs/pages/automate/agent-manager.md b/packages/kilo-docs/pages/automate/agent-manager.md index a6b566c47a..e9d4bfd17a 100644 --- a/packages/kilo-docs/pages/automate/agent-manager.md +++ b/packages/kilo-docs/pages/automate/agent-manager.md @@ -159,9 +159,9 @@ Each request can include 1-20 tasks. Each task must include at least one of `pro The companion `agent_manager_models` tool searches models and their supported reasoning variants on demand. Results are grouped by model name (with the offering providers listed for reference) and limited to 20 per call, so the full catalog is never added to the conversation context. -The same tool also manages existing sessions. It can return a compact overview of sections, worktrees, and local sessions, send a prompt to one managed session, or stop a managed session. Stopping aborts the session's active work and removes it from the panel, just like closing the session tab. +The same tool also manages existing sessions. It can return an overview of sections, worktrees, and local sessions, send a prompt to one managed session, stop a managed session, or move a session's worktree into a section. The overview includes section IDs, each section's assigned worktrees, worktree IDs, and session IDs. Use those exact IDs for a subsequent move. Moving accepts a section ID from the overview, or `null` to ungroup the worktree. Moving a session moves its whole worktree, including multi-version siblings. Local sessions cannot be assigned to a section. Stopping aborts the session's active work and removes it from the panel, just like closing the session tab. -The tool uses the `agent_manager` permission. Approval prompts are scoped to the requested capability, so approving `worktree` does not automatically approve `local`, an overview, or a targeted prompt. Prompting an existing managed session requires an explicit `prompt` approval the first time, even if Agent Manager session creation was previously approved broadly. Stopping a session likewise requires an explicit `stop` approval. +The tool uses the `agent_manager` permission. Approval prompts are scoped to the requested capability, so approving `worktree` does not automatically approve `local`, an overview, or a targeted prompt. Prompting an existing managed session requires an explicit `prompt` approval the first time, even if Agent Manager session creation was previously approved broadly. Stopping a session likewise requires an explicit `stop` approval, and moving a worktree requires an explicit `move` approval. ## Sections diff --git a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts index 56aa14f5b2..eec3f60c1c 100644 --- a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts +++ b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts @@ -242,6 +242,7 @@ export class AgentManagerProvider implements Disposable { }, stats: (refresh) => this.statsPoller.snapshot(refresh), prs: () => this.prBridge.snapshot(), + push: () => this.pushState(), managed: (id) => this.panelSessions.has(id) || !!this.state?.getSession(id), close: async (id) => { await this.onCloseSession(id) diff --git a/packages/kilo-vscode/src/agent-manager/orchestration-bridge.ts b/packages/kilo-vscode/src/agent-manager/orchestration-bridge.ts index 5d87563040..cb2fa2da09 100644 --- a/packages/kilo-vscode/src/agent-manager/orchestration-bridge.ts +++ b/packages/kilo-vscode/src/agent-manager/orchestration-bridge.ts @@ -7,6 +7,7 @@ import type { PRStatus } from "./types" import type { WorktreeStateManager } from "./WorktreeStateManager" import { OrchestrationError, + move, overview, prompt, sameManagedDirectory, @@ -26,11 +27,13 @@ type Request = | (RequestBase & { operation: "overview"; filter?: OverviewFilter }) | (RequestBase & { operation: "prompt"; targetSessionID: string; prompt: string }) | (RequestBase & { operation: "stop"; targetSessionID: string }) + | (RequestBase & { operation: "move"; targetSessionID: string; sectionID: string | null }) type Result = | { operation: "overview"; overview: Overview } | { operation: "prompt"; sessionID: string; delivered: true } | { operation: "stop"; sessionID: string; stopped: true } + | { operation: "move"; sessionID: string; sectionID: string | null; moved: true } interface Failure { code: FailureCode | "cancelled" | "disconnected" | "timeout" @@ -43,6 +46,7 @@ interface Options { state(): WorktreeStateManager | undefined stats(refresh?: boolean): Promise<{ worktrees: WorktreeStats[]; local?: LocalStats }> prs(): Map + push(): void managed(sessionID: string): boolean close(sessionID: string): Promise log(...args: unknown[]): void @@ -279,6 +283,19 @@ export class AgentManagerOrchestrationBridge { if (this.disposed || active.cancelled) return return { result: { operation: "prompt", sessionID: request.targetSessionID, delivered: true } } } + if (request.operation === "move") { + move({ state, sessionID: request.targetSessionID, sectionID: request.sectionID }) + this.options.push() + if (this.disposed || active.cancelled) return + return { + result: { + operation: "move", + sessionID: request.targetSessionID, + sectionID: request.sectionID, + moved: true, + }, + } + } if (!this.options.managed(request.targetSessionID)) { throw new OrchestrationError("unknown_session", "The session is not managed by this Agent Manager workspace") } diff --git a/packages/kilo-vscode/src/agent-manager/orchestration-domain.ts b/packages/kilo-vscode/src/agent-manager/orchestration-domain.ts index 79f0b6614a..b8b0828271 100644 --- a/packages/kilo-vscode/src/agent-manager/orchestration-domain.ts +++ b/packages/kilo-vscode/src/agent-manager/orchestration-domain.ts @@ -13,6 +13,7 @@ export type FailureCode = | "host_error" | "stale_session" | "unavailable_session" + | "unknown_section" | "unknown_session" | "workspace_unavailable" @@ -362,3 +363,20 @@ export async function prompt(input: { { throwOnError: true }, ) } + +export function move(input: { state: WorktreeStateManager; sessionID: string; sectionID: string | null }): void { + const session = input.state.getSession(input.sessionID) + if (!session) + throw new OrchestrationError("unknown_session", "The session is not managed by this Agent Manager workspace") + if (!session.worktreeId) { + if (input.sectionID === null) return + throw new OrchestrationError( + "unavailable_session", + "Only sessions attached to a worktree can be assigned to a section", + ) + } + if (input.sectionID !== null && !input.state.getSection(input.sectionID)) { + throw new OrchestrationError("unknown_section", "The target section is not managed by this Agent Manager workspace") + } + input.state.moveToSection([session.worktreeId], input.sectionID) +} diff --git a/packages/kilo-vscode/tests/unit/agent-manager-orchestration-bridge.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-orchestration-bridge.test.ts index 3c3dfdac08..2c3f845e34 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-orchestration-bridge.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-orchestration-bridge.test.ts @@ -45,6 +45,7 @@ describe("AgentManagerOrchestrationBridge", () => { const managed = new Set(["ses_target"]) const promptAsync = mock(async () => ({ data: undefined })) const close = mock(async () => undefined) + const push = mock(() => undefined) const client = { session: { get: mock(async () => ({ @@ -97,6 +98,7 @@ describe("AgentManagerOrchestrationBridge", () => { state: () => state, stats: async () => ({ worktrees: [] }), prs: () => new Map(), + push, managed: (id) => managed.has(id), close, log: () => undefined, @@ -106,7 +108,7 @@ describe("AgentManagerOrchestrationBridge", () => { { id: `event-${value.id}`, type: "kilocode.agent_manager.requested", properties: value } as SSEPayload, directory, ) - return { bridge, client, close, handlers, lists, managed, promptAsync, rejections, replies, request, status } + return { bridge, client, close, handlers, lists, managed, promptAsync, push, rejections, replies, request, status } } const request: AgentManagerRequest = { @@ -185,6 +187,52 @@ describe("AgentManagerOrchestrationBridge", () => { test.bridge.dispose() }) + it("moves its own worktree into a section and then ungroups it", async () => { + const test = harness() + const section = state.addSection("Review", null) + + test.request( + { + id: "amr_move", + sessionID: "ses_target", + operation: "move", + targetSessionID: "ses_target", + sectionID: section.id, + }, + dir, + ) + await waitFor(() => test.replies.length === 1) + + const worktreeID = state.getSession("ses_target")!.worktreeId! + expect(state.getWorktree(worktreeID)?.sectionId).toBe(section.id) + expect(test.push).toHaveBeenCalledTimes(1) + expect(test.replies[0]).toEqual({ + requestID: "amr_move", + directory: dir, + result: { operation: "move", sessionID: "ses_target", sectionID: section.id, moved: true }, + }) + + test.request( + { + id: "amr_ungroup", + sessionID: "ses_target", + operation: "move", + targetSessionID: "ses_target", + sectionID: null, + }, + dir, + ) + await waitFor(() => test.replies.length === 2) + + expect(state.getWorktree(worktreeID)?.sectionId).toBeUndefined() + expect(test.replies[1]).toEqual({ + requestID: "amr_ungroup", + directory: dir, + result: { operation: "move", sessionID: "ses_target", sectionID: null, moved: true }, + }) + test.bridge.dispose() + }) + it("stops a live panel session before it is persisted", async () => { const test = harness() test.managed.add("ses_live") diff --git a/packages/opencode/src/kilocode/agent-manager/protection.ts b/packages/opencode/src/kilocode/agent-manager/protection.ts new file mode 100644 index 0000000000..b8af70c0eb --- /dev/null +++ b/packages/opencode/src/kilocode/agent-manager/protection.ts @@ -0,0 +1,9 @@ +export function assertMutablePath(filepath: string) { + const parts = filepath.split(/[\\/]/) + const file = parts.at(-1) + const dir = parts.at(-2) + if (file !== "agent-manager.json" || ![".kilo", ".kilocode"].includes(dir ?? "")) return + throw new Error( + "Do not edit Agent Manager state directly. Use the agent_manager tool: call action=list to read section and session IDs, then call action=move with the returned IDs.", + ) +} diff --git a/packages/opencode/src/kilocode/agent-manager/protocol.ts b/packages/opencode/src/kilocode/agent-manager/protocol.ts index ef6c6f91ca..d6c66376c0 100644 --- a/packages/opencode/src/kilocode/agent-manager/protocol.ts +++ b/packages/opencode/src/kilocode/agent-manager/protocol.ts @@ -105,7 +105,14 @@ export const StopRequest = Schema.Struct({ targetSessionID: SessionID, }).annotate({ identifier: "AgentManagerStopRequest" }) -export const Request = Schema.Union([OverviewRequest, PromptRequest, StopRequest]).annotate({ +export const MoveRequest = Schema.Struct({ + ...Base, + operation: Schema.Literal("move"), + targetSessionID: SessionID, + sectionID: Schema.NullOr(ID), +}).annotate({ identifier: "AgentManagerMoveRequest" }) + +export const Request = Schema.Union([OverviewRequest, PromptRequest, StopRequest, MoveRequest]).annotate({ identifier: "AgentManagerRequest", }) export type Request = Schema.Schema.Type @@ -127,7 +134,14 @@ export const StopResult = Schema.Struct({ stopped: Schema.Literal(true), }).annotate({ identifier: "AgentManagerStopResult" }) -export const Result = Schema.Union([OverviewResult, PromptResult, StopResult]).annotate({ +export const MoveResult = Schema.Struct({ + operation: Schema.Literal("move"), + sessionID: SessionID, + sectionID: Schema.NullOr(ID), + moved: Schema.Literal(true), +}).annotate({ identifier: "AgentManagerMoveResult" }) + +export const Result = Schema.Union([OverviewResult, PromptResult, StopResult, MoveResult]).annotate({ identifier: "AgentManagerResult", }) export type Result = Schema.Schema.Type @@ -140,6 +154,7 @@ export const ErrorCode = Schema.Literals([ "stale_session", "timeout", "unavailable_session", + "unknown_section", "unknown_session", "workspace_unavailable", ]) diff --git a/packages/opencode/src/kilocode/permission/agent-manager.ts b/packages/opencode/src/kilocode/permission/agent-manager.ts index 17916b680d..672bee8b04 100644 --- a/packages/opencode/src/kilocode/permission/agent-manager.ts +++ b/packages/opencode/src/kilocode/permission/agent-manager.ts @@ -2,11 +2,11 @@ import { type Rule } from "./rule" export namespace AgentManagerPermission { /** - * Prompting or stopping an existing Agent Manager session has an external side effect. + * Prompting, stopping, or moving an existing Agent Manager session has an external side effect. * Broad approvals for legacy session creation must not silently grant it. */ export function harden(permission: string, pattern: string, rule: Rule): Rule { - if (permission !== "agent_manager" || !["prompt", "stop"].includes(pattern) || rule.action !== "allow") return rule + if (permission !== "agent_manager" || !["prompt", "stop", "move"].includes(pattern) || rule.action !== "allow") return rule if (rule.permission === "agent_manager" && rule.pattern === pattern) return rule return { permission, pattern, action: "ask" } } diff --git a/packages/opencode/src/kilocode/tool/agent-manager.ts b/packages/opencode/src/kilocode/tool/agent-manager.ts index 27c65f44cd..ba6e67619e 100644 --- a/packages/opencode/src/kilocode/tool/agent-manager.ts +++ b/packages/opencode/src/kilocode/tool/agent-manager.ts @@ -15,14 +15,20 @@ import { matchesQuery } from "./model-search" import DESCRIPTION from "./agent-manager.txt" const Task = Schema.Struct({ - prompt: Schema.optional(Schema.String).annotate({ description: "Initial prompt to send to the new session" }), - name: Schema.optional(Schema.String).annotate({ description: "Short display name for the Agent Manager card" }), - branchName: Schema.optional(Schema.String).annotate({ description: "Git branch name seed for worktree mode" }), - model: Schema.optional(Schema.String).annotate({ + prompt: Schema.optional(Schema.NullOr(Schema.String)).annotate({ + description: "Initial prompt to send to the new session", + }), + name: Schema.optional(Schema.NullOr(Schema.String)).annotate({ + description: "Short display name for the Agent Manager card", + }), + branchName: Schema.optional(Schema.NullOr(Schema.String)).annotate({ + description: "Git branch name seed for worktree mode", + }), + model: Schema.optional(Schema.NullOr(Schema.String)).annotate({ description: "Optional model override from agent_manager_models (e.g. 'Claude Opus 4.1'). Omit unless the user requests a different model. Agent Manager otherwise inherits the current turn's model. A qualified provider/model ID is also accepted to force a specific provider.", }), - variant: Schema.optional(Schema.String).annotate({ + variant: Schema.optional(Schema.NullOr(Schema.String)).annotate({ description: "Optional reasoning variant override from agent_manager_models. Specify it without model to override the inherited model's variant. Omit both to inherit the current turn's selection.", }), @@ -44,7 +50,7 @@ const StartParams = Schema.Struct({ mode: Schema.Literals(["worktree", "local"]).annotate({ description: "Use worktree for isolated git worktrees, or local for same-directory Agent Manager sessions", }), - versions: Schema.optional(Schema.Boolean).annotate({ + versions: Schema.optional(Schema.NullOr(Schema.Boolean)).annotate({ description: "Set true only when tasks are alternative versions of the same work to compare. Omit or false for independent sessions.", }), @@ -54,17 +60,22 @@ const StartParams = Schema.Struct({ }) const ListParams = Schema.Struct({ - action: Schema.Literal("list"), + action: Schema.Literal("list").annotate({ + description: + "Read the current Agent Manager sections, worktrees, and sessions before any assignment. This is the source of truth for section and session IDs.", + }), filter: Schema.optional( - Schema.Struct({ - sectionIDs: Schema.optional(Schema.Array(Schema.String).check(Schema.isMaxLength(100))), - states: Schema.optional( - Schema.Array(Schema.Literals(["idle", "busy", "retry", "offline", "waiting"])).check( - Schema.isMaxLength(5), + Schema.NullOr( + Schema.Struct({ + sectionIDs: Schema.optional(Schema.Array(Schema.String).check(Schema.isMaxLength(100))), + states: Schema.optional( + Schema.Array(Schema.Literals(["idle", "busy", "retry", "offline", "waiting"])).check(Schema.isMaxLength(5)), ), - ), - }), - ), + }), + ), + ).annotate({ + description: "Optional list filter. Omit this for an unfiltered overview when discovering assignments.", + }), }) const PromptParams = Schema.Struct({ @@ -80,16 +91,36 @@ const StopParams = Schema.Struct({ sessionID: SessionID, }) -export const Params = Schema.Union([StartParams, ListParams, PromptParams, StopParams]) +const MoveParams = Schema.Struct({ + action: Schema.Literal("move").annotate({ + description: "Move exactly one managed worktree by targeting one of its session IDs returned by action=list.", + }), + sessionID: SessionID.annotate({ + description: "Session ID returned by action=list. Do not use a worktree name, branch, or section name.", + }), + sectionID: Schema.NullOr(Schema.String).annotate({ + description: "Section ID returned by action=list. Use null to unassign the worktree from its current section.", + }), +}) + +export const Params = Schema.Union([StartParams, ListParams, PromptParams, StopParams, MoveParams]) const WireParams = Schema.Struct({ mode: Schema.optional(StartParams.fields.mode), versions: Schema.optional(StartParams.fields.versions), tasks: Schema.optional(StartParams.fields.tasks), - action: Schema.optional(Schema.Literals(["list", "prompt", "stop"])), + action: Schema.optional( + Schema.Literals(["list", "prompt", "stop", "move"]).annotate({ + description: + "Use list first to discover IDs and assignments. Use move only after list, once per worktree. Never edit .kilo/agent-manager.json for these operations.", + }), + ), filter: Schema.optional(ListParams.fields.filter), - sessionID: Schema.optional(PromptParams.fields.sessionID), + sessionID: Schema.optional( + SessionID.annotate({ description: "For move, use a session ID returned by action=list." }), + ), prompt: Schema.optional(PromptParams.fields.prompt), + sectionID: Schema.optional(MoveParams.fields.sectionID), }) type Input = Schema.Schema.Type @@ -169,9 +200,9 @@ function select( index: number, ): Selected { const base = { - ...(task.prompt !== undefined ? { prompt: task.prompt } : {}), - ...(task.name !== undefined ? { name: task.name } : {}), - ...(task.branchName !== undefined ? { branchName: task.branchName } : {}), + ...(task.prompt != null ? { prompt: task.prompt } : {}), + ...(task.name != null ? { name: task.name } : {}), + ...(task.branchName != null ? { branchName: task.branchName } : {}), } const value = task.model?.trim() const variant = task.variant?.trim() @@ -241,7 +272,7 @@ function select( export const AgentManagerTool = Tool.define< typeof Params, - { action: "start" | "list" | "prompt" | "stop"; requestID?: string; count?: number; sessionID?: string }, + { action: "start" | "list" | "prompt" | "stop" | "move"; requestID?: string; count?: number; sessionID?: string }, AgentManager.Service | Bus.Service | Provider.Service, "agent_manager" >( @@ -250,10 +281,18 @@ export const AgentManagerTool = Tool.define< const bus = yield* Bus.Service const host = yield* AgentManager.Service const provider = yield* Provider.Service + const wire = ToolJsonSchema.fromSchema(WireParams) + const section = wire.properties?.sectionID + if (section && typeof section === "object" && wire.properties) { + wire.properties.sectionID = { + anyOf: [{ type: "string", minLength: 1 }, { type: "null" }], + description: "Section ID returned by action=list. Use null to unassign the worktree from its current section.", + } + } return { description: DESCRIPTION, parameters: Params, - jsonSchema: ToolJsonSchema.fromSchema(WireParams), + jsonSchema: wire, execute: (params, ctx) => Effect.gen(function* () { if ("action" in params) { @@ -265,7 +304,7 @@ export const AgentManagerTool = Tool.define< metadata: { action: "list" }, }) const result = yield* run( - host.request({ operation: "overview", sessionID: ctx.sessionID, filter: params.filter }), + host.request({ operation: "overview", sessionID: ctx.sessionID, filter: params.filter ?? undefined }), ctx.abort, ) if (result.operation !== "overview") @@ -276,7 +315,15 @@ export const AgentManagerTool = Tool.define< result.overview.sections.reduce((sum, section) => sum + section.worktrees.length, 0) return { title: "Agent Manager overview", - output: JSON.stringify(result.overview), + output: JSON.stringify( + { + instructions: + "This overview is the source of truth. Use sections[].id as sectionID and sessions[].id/session.id as sessionID for action=move. Do not edit .kilo/agent-manager.json.", + ...result.overview, + }, + null, + 2, + ), metadata: { action: "list", count }, } } @@ -304,26 +351,50 @@ export const AgentManagerTool = Tool.define< metadata: { action: "prompt", sessionID: result.sessionID }, } } + if (params.action === "stop") { + yield* ctx.ask({ + permission: "agent_manager", + patterns: ["stop"], + always: ["stop"], + metadata: { action: "stop", sessionID: params.sessionID }, + }) + const result = yield* run( + host.request({ + operation: "stop", + sessionID: ctx.sessionID, + targetSessionID: params.sessionID, + }), + ctx.abort, + ) + if (result.operation !== "stop") + return yield* Effect.die(new Error("Agent Manager host returned the wrong result type")) + return { + title: "Session stopped", + output: `Stopped Agent Manager session ${result.sessionID} and removed it from Agent Manager.`, + metadata: { action: "stop", sessionID: result.sessionID }, + } + } yield* ctx.ask({ permission: "agent_manager", - patterns: ["stop"], - always: ["stop"], - metadata: { action: "stop", sessionID: params.sessionID }, + patterns: ["move"], + always: ["move"], + metadata: { action: "move", sessionID: params.sessionID, sectionID: params.sectionID }, }) const result = yield* run( host.request({ - operation: "stop", + operation: "move", sessionID: ctx.sessionID, targetSessionID: params.sessionID, + sectionID: params.sectionID, }), ctx.abort, ) - if (result.operation !== "stop") + if (result.operation !== "move") return yield* Effect.die(new Error("Agent Manager host returned the wrong result type")) return { - title: "Session stopped", - output: `Stopped Agent Manager session ${result.sessionID} and removed it from Agent Manager.`, - metadata: { action: "stop", sessionID: result.sessionID }, + title: "Session moved", + output: `Moved Agent Manager session ${result.sessionID} to ${result.sectionID ?? "Ungrouped"}.`, + metadata: { action: "move", sessionID: result.sessionID, sectionID: result.sectionID }, } } @@ -380,7 +451,7 @@ export const AgentManagerTool = Tool.define< sessionID: ctx.sessionID, sandboxInheritanceToken, mode: params.mode, - versions: params.versions, + ...(params.versions != null ? { versions: params.versions } : {}), tasks, }) diff --git a/packages/opencode/src/kilocode/tool/agent-manager.txt b/packages/opencode/src/kilocode/tool/agent-manager.txt index 813de72c5f..80be0f58e4 100644 --- a/packages/opencode/src/kilocode/tool/agent-manager.txt +++ b/packages/opencode/src/kilocode/tool/agent-manager.txt @@ -1,6 +1,6 @@ -Inspect and orchestrate Agent Manager sessions, or start new sessions, in the VS Code extension. +Inspect and orchestrate Agent Manager sessions, or start new sessions, in the VS Code extension. Use this tool for Agent Manager sections and assignments. Do not edit `.kilo/agent-manager.json` directly: it is persisted UI/recovery state, not the Agent Manager API, and manual patches can overwrite live state. -Use `action: "list"` to inspect the compact Agent Manager overview, `action: "prompt"` to send one instruction to one existing managed session, and `action: "stop"` to stop and remove one managed session. List results include user-defined sections, ungrouped worktrees, and managed local sessions. Optional filters can narrow by section ID or by `idle`, `busy`, `retry`, `offline`, or `waiting` state. Prompting and stopping are targeted only: they do not broadcast or create sessions, and prompting does not wait for the target to finish. +Use `action: "list"` to inspect the Agent Manager overview, `action: "prompt"` to send one instruction to one existing managed session, `action: "stop"` to stop and remove one managed session, and `action: "move"` to move one session's worktree into a section or ungroup it. For any assignment request, the required sequence is: (1) call `agent_manager` with `{ "action": "list" }`; (2) read the returned `sections[].id`, `sections[].worktrees[].session.id` or `sessions[].id`, and `ungrouped[].session.id` or `sessions[].sessions[].id`; (3) call `agent_manager` with `{ "action": "move", "sessionID": "", "sectionID": "" }` once for each worktree; (4) use `sectionID: null` to unassign. Never invent IDs, use section names instead of IDs, or edit `.kilo/agent-manager.json`. The `list` result is the source of truth for IDs and assignments: each `sections` entry includes the section `id`, name, and its assigned `worktrees`; each worktree includes its worktree `id` and its session ID(s) in `session` or `sessions`; `ungrouped` lists worktrees that have no section; and `local.sessions` lists local sessions that cannot be assigned to a section. For `move`, pass the target session's ID as `sessionID` and a section ID as `sectionID`; pass `null` to unassign it. Optional filters can narrow by section ID or by `idle`, `busy`, `retry`, `offline`, or `waiting` state. Prompting, stopping, and moving are targeted only: they do not broadcast or create sessions, and prompting does not wait for the target to finish. Moving a session moves its whole worktree, including multi-version siblings; local sessions cannot be assigned to a section. To start sessions, keep using the existing `mode` and `tasks` input without an action. Use start mode when the user explicitly asks you to fan out work into Agent Manager, create Agent Manager worktrees, or start multiple Agent Manager sessions for independent tasks. diff --git a/packages/opencode/src/server/routes/instance/httpapi/public.ts b/packages/opencode/src/server/routes/instance/httpapi/public.ts index 2d5001c1cf..b7dd0cceb2 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/public.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/public.ts @@ -116,6 +116,7 @@ function matchLegacyOpenApi(input: Record) { } normalizeComponentNames(spec) collapseDuplicateComponents(spec) + restoreAgentManagerNulls(spec) // kilocode_change applyLegacySchemaOverrides(spec) normalizeComponentDescriptions(spec) addLegacyErrorSchemas(spec) @@ -517,6 +518,20 @@ function stripOptionalNull(schema: OpenApiSchema): OpenApiSchema { return schema } +// kilocode_change start - preserve nullable Agent Manager move targets in generated SDK schemas +function restoreAgentManagerNulls(spec: OpenApiSpec) { + const schemas = spec.components?.schemas + if (!schemas) return + for (const name of ["AgentManagerMoveRequest", "AgentManagerMoveResult"]) { + const schema = schemas[name] + if (!schema?.properties?.sectionID) continue + schema.properties.sectionID = { + anyOf: [schema.properties.sectionID, { type: "null" }], + } + } +} +// kilocode_change end + function isEmptyObjectUnion(schema: OpenApiSchema) { const options = schema.anyOf ?? schema.oneOf return options?.length === 2 && options.some(isBareObjectSchema) && options.some(isBareArraySchema) diff --git a/packages/opencode/src/tool/apply_patch.ts b/packages/opencode/src/tool/apply_patch.ts index 979e1dd5e3..7f0188676c 100644 --- a/packages/opencode/src/tool/apply_patch.ts +++ b/packages/opencode/src/tool/apply_patch.ts @@ -17,6 +17,7 @@ import { ConfigValidation } from "../kilocode/config-validation" // kilocode_cha import * as EncodedIO from "../kilocode/tool/encoded-io" // kilocode_change import { Format } from "../format" import * as Bom from "@/util/bom" +import { assertMutablePath } from "../kilocode/agent-manager/protection" // kilocode_change export const Parameters = Schema.Struct({ patchText: Schema.String.annotate({ description: "The full patch text that describes all changes to be made" }), @@ -75,6 +76,7 @@ export const ApplyPatchTool = Tool.define( for (const hunk of hunks) { const filePath = path.resolve(instance.directory, hunk.path) + assertMutablePath(filePath) // kilocode_change yield* assertExternalDirectoryEffect(ctx, filePath) switch (hunk.type) { diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts index 4498be3da6..1afd351bdf 100644 --- a/packages/opencode/src/tool/edit.ts +++ b/packages/opencode/src/tool/edit.ts @@ -22,6 +22,7 @@ import { filterDiagnostics } from "./diagnostics" // kilocode_change import { ConfigValidation } from "../kilocode/config-validation" // kilocode_change import * as EncodedIO from "../kilocode/tool/encoded-io" // kilocode_change import * as Encoding from "../kilocode/encoding" // kilocode_change +import { assertMutablePath } from "../kilocode/agent-manager/protection" // kilocode_change const MAX_DIFF_CONTENT = 500_000 // kilocode_change @@ -106,6 +107,7 @@ export const EditTool = Tool.define( const filePath = path.isAbsolute(params.filePath) ? params.filePath : path.join(instance.directory, params.filePath) + assertMutablePath(filePath) // kilocode_change yield* assertExternalDirectoryEffect(ctx, filePath) let diff = "" diff --git a/packages/opencode/src/tool/write.ts b/packages/opencode/src/tool/write.ts index 546fb571ce..b654e581b4 100644 --- a/packages/opencode/src/tool/write.ts +++ b/packages/opencode/src/tool/write.ts @@ -16,6 +16,7 @@ import { assertExternalDirectoryEffect } from "./external-directory" import { filterDiagnostics } from "./diagnostics" // kilocode_change import { ConfigValidation } from "../kilocode/config-validation" // kilocode_change import * as EncodedIO from "../kilocode/tool/encoded-io" // kilocode_change +import { assertMutablePath } from "../kilocode/agent-manager/protection" // kilocode_change import * as Bom from "@/util/bom" const MAX_PROJECT_DIAGNOSTICS_FILES = 5 @@ -44,6 +45,7 @@ export const WriteTool = Tool.define( const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(instance.directory, params.filePath) + assertMutablePath(filepath) // kilocode_change yield* assertExternalDirectoryEffect(ctx, filepath) const exists = yield* fs.existsSafe(filepath) diff --git a/packages/opencode/test/kilocode/agent-manager-protection.test.ts b/packages/opencode/test/kilocode/agent-manager-protection.test.ts new file mode 100644 index 0000000000..cf5ff8975a --- /dev/null +++ b/packages/opencode/test/kilocode/agent-manager-protection.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, test } from "bun:test" +import { assertMutablePath } from "@/kilocode/agent-manager/protection" + +describe("Agent Manager state protection", () => { + test("rejects direct edits to Agent Manager state", () => { + expect(() => assertMutablePath("/workspace/.kilo/agent-manager.json")).toThrow( + "Do not edit Agent Manager state directly", + ) + expect(() => assertMutablePath("/workspace/.kilocode/agent-manager.json")).toThrow( + "Do not edit Agent Manager state directly", + ) + }) + + test("allows ordinary project files", () => { + expect(() => assertMutablePath("/workspace/.kilo/settings.json")).not.toThrow() + expect(() => assertMutablePath("/workspace/src/agent-manager.json")).not.toThrow() + }) +}) diff --git a/packages/opencode/test/kilocode/agent-manager-tool.test.ts b/packages/opencode/test/kilocode/agent-manager-tool.test.ts index 9091fcb972..974b5bf543 100644 --- a/packages/opencode/test/kilocode/agent-manager-tool.test.ts +++ b/packages/opencode/test/kilocode/agent-manager-tool.test.ts @@ -159,7 +159,20 @@ describe("agent_manager tool", () => { expect(schema.oneOf).toBeUndefined() expect(schema.allOf).toBeUndefined() const action = schema.properties?.action - expect(action && typeof action === "object" ? action.enum : undefined).toEqual(["list", "prompt", "stop"]) + expect(action && typeof action === "object" ? action.enum : undefined).toEqual(["list", "prompt", "stop", "move"]) + expect(action && typeof action === "object" ? action.description : undefined).toContain("Use list first") + expect(action && typeof action === "object" ? action.description : undefined).toContain("Never edit") + expect(schema.properties?.sessionID).toEqual( + expect.objectContaining({ description: expect.stringContaining("returned by action=list") }), + ) + expect(schema.properties?.sectionID).toEqual( + expect.objectContaining({ description: expect.stringContaining("Use null to unassign") }), + ) + expect(schema.properties?.sectionID).toEqual( + expect.objectContaining({ + anyOf: expect.arrayContaining([expect.objectContaining({ type: "string" }), { type: "null" }]), + }), + ) expect(Object.keys(schema.properties ?? {})).toEqual([ "mode", "versions", @@ -168,6 +181,7 @@ describe("agent_manager tool", () => { "filter", "sessionID", "prompt", + "sectionID", ]) }) @@ -226,7 +240,7 @@ describe("agent_manager tool", () => { const result = await rt.runPromise( provideTmpdirInstance(() => tool.execute( - { action: "list" }, + { action: "list", filter: null }, { ...ctx, ask: (input: unknown) => Effect.sync(() => permissions.push(input)) }, ), ).pipe(Effect.scoped), @@ -242,6 +256,8 @@ describe("agent_manager tool", () => { ]) expect(requests).toEqual([{ operation: "overview", sessionID: ctx.sessionID, filter: undefined }]) expect(JSON.parse(result.output)).toEqual({ + instructions: + "This overview is the source of truth. Use sections[].id as sectionID and sessions[].id/session.id as sessionID for action=move. Do not edit .kilo/agent-manager.json.", sections: [], ungrouped: [ { @@ -345,6 +361,58 @@ describe("agent_manager tool", () => { await rt.dispose() }) + test("moves one existing session with a separate mutation permission pattern", async () => { + const requests: unknown[] = [] + const rt = makeRuntime("test", { + request: (input) => + Effect.sync(() => { + requests.push(input) + return { + operation: "move" as const, + sessionID: SessionID.make("ses_target"), + sectionID: "sec_review", + moved: true as const, + } + }), + }) + const tool = await rt.runPromise( + Effect.gen(function* () { + return yield* Tool.init(yield* AgentManagerTool) + }), + ) + const permissions: unknown[] = [] + const result = await rt.runPromise( + provideTmpdirInstance(() => + tool.execute( + { action: "move", sessionID: SessionID.make("ses_target"), sectionID: "sec_review" }, + { ...ctx, ask: (input: unknown) => Effect.sync(() => permissions.push(input)) }, + ), + ).pipe(Effect.scoped), + ) + + expect(permissions).toEqual([ + { + permission: "agent_manager", + patterns: ["move"], + always: ["move"], + metadata: { action: "move", sessionID: "ses_target", sectionID: "sec_review" }, + }, + ]) + expect(requests).toEqual([ + { + operation: "move", + sessionID: ctx.sessionID, + targetSessionID: "ses_target", + sectionID: "sec_review", + }, + ]) + expect(result.output).toContain("sec_review") + expect(result.metadata).toEqual( + expect.objectContaining({ action: "move", sessionID: "ses_target", sectionID: "sec_review" }), + ) + await rt.dispose() + }) + test("inherits the latest invoking model and variant when omitted", async () => { const task = await publish(runtime, { prompt: "Fix" }, [ message("msg_current", "kilo", "kilo/shared", "low", 2), diff --git a/packages/opencode/test/kilocode/permission/agent-manager-prompt.test.ts b/packages/opencode/test/kilocode/permission/agent-manager-prompt.test.ts index da18967eb2..b976b1478d 100644 --- a/packages/opencode/test/kilocode/permission/agent-manager-prompt.test.ts +++ b/packages/opencode/test/kilocode/permission/agent-manager-prompt.test.ts @@ -7,6 +7,7 @@ describe("Agent Manager side-effect permissions", () => { test("requires consent despite a broad Agent Manager allow rule", () => { expect(Permission.resolve("agent_manager", "prompt", broad).action).toBe("ask") expect(Permission.resolve("agent_manager", "stop", broad).action).toBe("ask") + expect(Permission.resolve("agent_manager", "move", broad).action).toBe("ask") expect(Permission.resolve("agent_manager", "local", broad).action).toBe("allow") expect(Permission.resolve("agent_manager", "worktree", broad).action).toBe("allow") }) @@ -15,6 +16,7 @@ describe("Agent Manager side-effect permissions", () => { const rules = [{ permission: "*", pattern: "*", action: "allow" as const }] expect(Permission.resolve("agent_manager", "prompt", rules).action).toBe("ask") expect(Permission.resolve("agent_manager", "stop", rules).action).toBe("ask") + expect(Permission.resolve("agent_manager", "move", rules).action).toBe("ask") }) test("requires consent despite a saved wildcard approval", () => { @@ -22,11 +24,13 @@ describe("Agent Manager side-effect permissions", () => { const saved = [{ permission: "agent_manager", pattern: "*", action: "allow" as const }] expect(Permission.resolve("agent_manager", "prompt", rules, saved).action).toBe("ask") expect(Permission.resolve("agent_manager", "stop", rules, saved).action).toBe("ask") + expect(Permission.resolve("agent_manager", "move", rules, saved).action).toBe("ask") }) test("allows only explicit side-effect approvals", () => { - const rules = Permission.fromConfig({ agent_manager: { prompt: "allow", stop: "allow" } }) + const rules = Permission.fromConfig({ agent_manager: { prompt: "allow", stop: "allow", move: "allow" } }) expect(Permission.resolve("agent_manager", "prompt", rules).action).toBe("allow") expect(Permission.resolve("agent_manager", "stop", rules).action).toBe("allow") + expect(Permission.resolve("agent_manager", "move", rules).action).toBe("allow") }) }) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index e5f39340d5..b95780c020 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -281,7 +281,19 @@ export type AgentManagerStopRequest = { targetSessionID: string } -export type AgentManagerRequest = AgentManagerOverviewRequest | AgentManagerPromptRequest | AgentManagerStopRequest +export type AgentManagerMoveRequest = { + id: AgentManagerRequestId + sessionID: string + operation: "move" + targetSessionID: string + sectionID: string | null +} + +export type AgentManagerRequest = + | AgentManagerOverviewRequest + | AgentManagerPromptRequest + | AgentManagerStopRequest + | AgentManagerMoveRequest export type NotebookRequestId = string @@ -3390,7 +3402,18 @@ export type AgentManagerStopResult = { stopped: true } -export type AgentManagerResult = AgentManagerOverviewResult | AgentManagerPromptResult | AgentManagerStopResult +export type AgentManagerMoveResult = { + operation: "move" + sessionID: string + sectionID: string | null + moved: true +} + +export type AgentManagerResult = + | AgentManagerOverviewResult + | AgentManagerPromptResult + | AgentManagerStopResult + | AgentManagerMoveResult export type AgentManagerFailure = { code: @@ -3401,6 +3424,7 @@ export type AgentManagerFailure = { | "stale_session" | "timeout" | "unavailable_session" + | "unknown_section" | "unknown_session" | "workspace_unavailable" message: string diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 730ec61c4c..6e46bad83b 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -23784,6 +23784,13 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "replayExited", + "schema": { + "type": "string" + } } ], "security": [], @@ -24634,6 +24641,9 @@ { "$ref": "#/components/schemas/EventSandboxStatusChanged" }, + { + "$ref": "#/components/schemas/EventLspClientDiagnostics" + }, { "$ref": "#/components/schemas/EventSuggestionShown" }, @@ -24661,9 +24671,6 @@ { "$ref": "#/components/schemas/EventKilo-sessionsRemote-status-changed" }, - { - "$ref": "#/components/schemas/EventLspClientDiagnostics" - }, { "$ref": "#/components/schemas/EventMemoryStatus1" }, @@ -24679,6 +24686,9 @@ { "$ref": "#/components/schemas/EventIndexingWarning" }, + { + "$ref": "#/components/schemas/EventModels-devRefreshed" + }, { "$ref": "#/components/schemas/EventServerConnected" }, @@ -24847,9 +24857,6 @@ { "$ref": "#/components/schemas/EventSessionError" }, - { - "$ref": "#/components/schemas/EventModels-devRefreshed" - }, { "$ref": "#/components/schemas/EventInstallationUpdated" }, @@ -24857,13 +24864,7 @@ "$ref": "#/components/schemas/EventInstallationUpdate-available" }, { - "$ref": "#/components/schemas/EventPermissionAsked" - }, - { - "$ref": "#/components/schemas/EventPermissionReplied" - }, - { - "$ref": "#/components/schemas/EventReferenceUpdated" + "$ref": "#/components/schemas/EventFileEdited" }, { "$ref": "#/components/schemas/EventPermissionV2Asked" @@ -24872,10 +24873,10 @@ "$ref": "#/components/schemas/EventPermissionV2Replied" }, { - "$ref": "#/components/schemas/EventProjectDirectoriesUpdated" + "$ref": "#/components/schemas/EventReferenceUpdated" }, { - "$ref": "#/components/schemas/EventFileEdited" + "$ref": "#/components/schemas/EventProjectDirectoriesUpdated" }, { "$ref": "#/components/schemas/EventFileWatcherUpdated" @@ -24904,6 +24905,15 @@ { "$ref": "#/components/schemas/EventTodoUpdated" }, + { + "$ref": "#/components/schemas/EventLspUpdated" + }, + { + "$ref": "#/components/schemas/EventPermissionAsked" + }, + { + "$ref": "#/components/schemas/EventPermissionReplied" + }, { "$ref": "#/components/schemas/EventSessionStatus" }, @@ -24922,9 +24932,6 @@ { "$ref": "#/components/schemas/EventVcsBranchUpdated" }, - { - "$ref": "#/components/schemas/EventLspUpdated" - }, { "$ref": "#/components/schemas/EventWorkspaceReady" }, @@ -25462,6 +25469,40 @@ "required": ["id", "sessionID", "operation", "targetSessionID"], "additionalProperties": false }, + "AgentManagerMoveRequest": { + "type": "object", + "properties": { + "id": { + "$ref": "#/components/schemas/AgentManagerRequestID" + }, + "sessionID": { + "type": "string", + "pattern": "^ses" + }, + "operation": { + "type": "string", + "enum": ["move"] + }, + "targetSessionID": { + "type": "string", + "pattern": "^ses" + }, + "sectionID": { + "anyOf": [ + { + "type": "string", + "minLength": 1, + "maxLength": 200 + }, + { + "type": "null" + } + ] + } + }, + "required": ["id", "sessionID", "operation", "targetSessionID", "sectionID"], + "additionalProperties": false + }, "AgentManagerRequest": { "anyOf": [ { @@ -25472,6 +25513,9 @@ }, { "$ref": "#/components/schemas/AgentManagerStopRequest" + }, + { + "$ref": "#/components/schemas/AgentManagerMoveRequest" } ] }, @@ -27827,6 +27871,9 @@ { "$ref": "#/components/schemas/EventSandboxStatusChanged" }, + { + "$ref": "#/components/schemas/EventLspClientDiagnostics" + }, { "$ref": "#/components/schemas/EventSuggestionShown" }, @@ -27854,9 +27901,6 @@ { "$ref": "#/components/schemas/EventKilo-sessionsRemote-status-changed" }, - { - "$ref": "#/components/schemas/EventLspClientDiagnostics" - }, { "$ref": "#/components/schemas/EventMemoryStatus" }, @@ -27872,6 +27916,9 @@ { "$ref": "#/components/schemas/EventIndexingWarning" }, + { + "$ref": "#/components/schemas/EventModels-devRefreshed" + }, { "$ref": "#/components/schemas/EventServerConnected" }, @@ -28040,9 +28087,6 @@ { "$ref": "#/components/schemas/EventSessionError" }, - { - "$ref": "#/components/schemas/EventModels-devRefreshed" - }, { "$ref": "#/components/schemas/EventInstallationUpdated" }, @@ -28050,13 +28094,7 @@ "$ref": "#/components/schemas/EventInstallationUpdate-available" }, { - "$ref": "#/components/schemas/EventPermissionAsked" - }, - { - "$ref": "#/components/schemas/EventPermissionReplied" - }, - { - "$ref": "#/components/schemas/EventReferenceUpdated" + "$ref": "#/components/schemas/EventFileEdited" }, { "$ref": "#/components/schemas/EventPermissionV2Asked" @@ -28065,10 +28103,10 @@ "$ref": "#/components/schemas/EventPermissionV2Replied" }, { - "$ref": "#/components/schemas/EventProjectDirectoriesUpdated" + "$ref": "#/components/schemas/EventReferenceUpdated" }, { - "$ref": "#/components/schemas/EventFileEdited" + "$ref": "#/components/schemas/EventProjectDirectoriesUpdated" }, { "$ref": "#/components/schemas/EventFileWatcherUpdated" @@ -28097,6 +28135,15 @@ { "$ref": "#/components/schemas/EventTodoUpdated" }, + { + "$ref": "#/components/schemas/EventLspUpdated" + }, + { + "$ref": "#/components/schemas/EventPermissionAsked" + }, + { + "$ref": "#/components/schemas/EventPermissionReplied" + }, { "$ref": "#/components/schemas/EventSessionStatus" }, @@ -28115,9 +28162,6 @@ { "$ref": "#/components/schemas/EventVcsBranchUpdated" }, - { - "$ref": "#/components/schemas/EventLspUpdated" - }, { "$ref": "#/components/schemas/EventWorkspaceReady" }, @@ -34810,6 +34854,37 @@ "required": ["operation", "sessionID", "stopped"], "additionalProperties": false }, + "AgentManagerMoveResult": { + "type": "object", + "properties": { + "operation": { + "type": "string", + "enum": ["move"] + }, + "sessionID": { + "type": "string", + "pattern": "^ses" + }, + "sectionID": { + "anyOf": [ + { + "type": "string", + "minLength": 1, + "maxLength": 200 + }, + { + "type": "null" + } + ] + }, + "moved": { + "type": "boolean", + "enum": [true] + } + }, + "required": ["operation", "sessionID", "sectionID", "moved"], + "additionalProperties": false + }, "AgentManagerResult": { "anyOf": [ { @@ -34820,6 +34895,9 @@ }, { "$ref": "#/components/schemas/AgentManagerStopResult" + }, + { + "$ref": "#/components/schemas/AgentManagerMoveResult" } ] }, @@ -34836,6 +34914,7 @@ "stale_session", "timeout", "unavailable_session", + "unknown_section", "unknown_session", "workspace_unavailable" ] @@ -35821,6 +35900,33 @@ "required": ["id", "type", "properties"], "additionalProperties": false }, + "EventLspClientDiagnostics": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["lsp.client.diagnostics"] + }, + "properties": { + "type": "object", + "properties": { + "serverID": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": ["serverID", "path"], + "additionalProperties": false + } + }, + "required": ["id", "type", "properties"], + "additionalProperties": false + }, "EventSuggestionShown": { "type": "object", "properties": { @@ -36117,33 +36223,6 @@ "required": ["id", "type", "properties"], "additionalProperties": false }, - "EventLspClientDiagnostics": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "enum": ["lsp.client.diagnostics"] - }, - "properties": { - "type": "object", - "properties": { - "serverID": { - "type": "string" - }, - "path": { - "type": "string" - } - }, - "required": ["serverID", "path"], - "additionalProperties": false - } - }, - "required": ["id", "type", "properties"], - "additionalProperties": false - }, "EventMemoryStatus": { "type": "object", "properties": { @@ -37214,6 +37293,24 @@ "required": ["id", "type", "properties"], "additionalProperties": false }, + "EventModels-devRefreshed": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["models-dev.refreshed"] + }, + "properties": { + "type": "object", + "properties": {} + } + }, + "required": ["id", "type", "properties"], + "additionalProperties": false + }, "EventServerConnected": { "type": "object", "properties": { @@ -39256,24 +39353,6 @@ "required": ["id", "type", "properties"], "additionalProperties": false }, - "EventModels-devRefreshed": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "enum": ["models-dev.refreshed"] - }, - "properties": { - "type": "object", - "properties": {} - } - }, - "required": ["id", "type", "properties"], - "additionalProperties": false - }, "EventInstallationUpdated": { "type": "object", "properties": { @@ -39322,7 +39401,7 @@ "required": ["id", "type", "properties"], "additionalProperties": false }, - "EventPermissionAsked": { + "EventFileEdited": { "type": "object", "properties": { "id": { @@ -39330,109 +39409,22 @@ }, "type": { "type": "string", - "enum": ["permission.asked"] + "enum": ["file.edited"] }, "properties": { "type": "object", "properties": { - "id": { - "type": "string", - "pattern": "^per" - }, - "sessionID": { - "type": "string", - "pattern": "^ses" - }, - "permission": { + "file": { "type": "string" - }, - "patterns": { - "type": "array", - "items": { - "type": "string" - } - }, - "metadata": { - "type": "object" - }, - "always": { - "type": "array", - "items": { - "type": "string" - } - }, - "tool": { - "type": "object", - "properties": { - "messageID": { - "type": "string" - }, - "callID": { - "type": "string" - } - }, - "required": ["messageID", "callID"], - "additionalProperties": false } }, - "required": ["id", "sessionID", "permission", "patterns", "metadata", "always"], + "required": ["file"], "additionalProperties": false } }, "required": ["id", "type", "properties"], "additionalProperties": false }, - "EventPermissionReplied": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "enum": ["permission.replied"] - }, - "properties": { - "type": "object", - "properties": { - "sessionID": { - "type": "string", - "pattern": "^ses" - }, - "requestID": { - "type": "string", - "pattern": "^per" - }, - "reply": { - "type": "string", - "enum": ["once", "always", "reject"] - } - }, - "required": ["sessionID", "requestID", "reply"], - "additionalProperties": false - } - }, - "required": ["id", "type", "properties"], - "additionalProperties": false - }, - "EventReferenceUpdated": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "enum": ["reference.updated"] - }, - "properties": { - "type": "object", - "properties": {} - } - }, - "required": ["id", "type", "properties"], - "additionalProperties": false - }, "PermissionV2Source": { "type": "object", "properties": { @@ -39536,6 +39528,24 @@ "required": ["id", "type", "properties"], "additionalProperties": false }, + "EventReferenceUpdated": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["reference.updated"] + }, + "properties": { + "type": "object", + "properties": {} + } + }, + "required": ["id", "type", "properties"], + "additionalProperties": false + }, "EventProjectDirectoriesUpdated": { "type": "object", "properties": { @@ -39560,30 +39570,6 @@ "required": ["id", "type", "properties"], "additionalProperties": false }, - "EventFileEdited": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "enum": ["file.edited"] - }, - "properties": { - "type": "object", - "properties": { - "file": { - "type": "string" - } - }, - "required": ["file"], - "additionalProperties": false - } - }, - "required": ["id", "type", "properties"], - "additionalProperties": false - }, "EventFileWatcherUpdated": { "type": "object", "properties": { @@ -39910,6 +39896,117 @@ "required": ["id", "type", "properties"], "additionalProperties": false }, + "EventLspUpdated": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["lsp.updated"] + }, + "properties": { + "type": "object", + "properties": {} + } + }, + "required": ["id", "type", "properties"], + "additionalProperties": false + }, + "EventPermissionAsked": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["permission.asked"] + }, + "properties": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^per" + }, + "sessionID": { + "type": "string", + "pattern": "^ses" + }, + "permission": { + "type": "string" + }, + "patterns": { + "type": "array", + "items": { + "type": "string" + } + }, + "metadata": { + "type": "object" + }, + "always": { + "type": "array", + "items": { + "type": "string" + } + }, + "tool": { + "type": "object", + "properties": { + "messageID": { + "type": "string" + }, + "callID": { + "type": "string" + } + }, + "required": ["messageID", "callID"], + "additionalProperties": false + } + }, + "required": ["id", "sessionID", "permission", "patterns", "metadata", "always"], + "additionalProperties": false + } + }, + "required": ["id", "type", "properties"], + "additionalProperties": false + }, + "EventPermissionReplied": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["permission.replied"] + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "pattern": "^ses" + }, + "requestID": { + "type": "string", + "pattern": "^per" + }, + "reply": { + "type": "string", + "enum": ["once", "always", "reject"] + } + }, + "required": ["sessionID", "requestID", "reply"], + "additionalProperties": false + } + }, + "required": ["id", "type", "properties"], + "additionalProperties": false + }, "EventSessionStatus": { "type": "object", "properties": { @@ -40130,24 +40227,6 @@ "required": ["id", "type", "properties"], "additionalProperties": false }, - "EventLspUpdated": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "enum": ["lsp.updated"] - }, - "properties": { - "type": "object", - "properties": {} - } - }, - "required": ["id", "type", "properties"], - "additionalProperties": false - }, "EventWorkspaceReady": { "type": "object", "properties": {