indusagi-coding-agent 0.2.5 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +9 -5
- package/dist/entry.js +14267 -24282
- package/dist/guardrails.js +924 -7867
- package/dist/index.js +13706 -23775
- package/package.json +3 -2
- package/src/_decls/entry.ts +18 -0
- package/src/_decls/guardrails.ts +35 -0
- package/src/_decls/index.ts +26 -0
- package/src/addons/contract.ts +236 -0
- package/src/addons/dispatch/event-dispatcher.ts +164 -0
- package/src/addons/dispatch/index.ts +25 -0
- package/src/addons/dispatch/tool-interceptor.ts +208 -0
- package/src/addons/host.ts +225 -0
- package/src/addons/index.ts +112 -0
- package/src/addons/manifest.ts +158 -0
- package/src/addons/sandbox.ts +170 -0
- package/src/addons/surface.ts +78 -0
- package/src/boot/auth-vault.ts +195 -0
- package/src/boot/boot.ts +138 -0
- package/src/boot/contract.ts +238 -0
- package/src/boot/heap.ts +59 -0
- package/src/boot/index.ts +28 -0
- package/src/boot/invocation.ts +93 -0
- package/src/boot/runners/addon-wiring.ts +153 -0
- package/src/boot/runners/checkpoint.ts +169 -0
- package/src/boot/runners/delegate-runner.ts +294 -0
- package/src/boot/runners/index.ts +13 -0
- package/src/boot/runners/link-runner.ts +45 -0
- package/src/boot/runners/memdir.ts +168 -0
- package/src/boot/runners/oneshot-runner.ts +58 -0
- package/src/boot/runners/read-state.ts +90 -0
- package/src/boot/runners/registry.ts +42 -0
- package/src/boot/runners/repl-runner.ts +143 -0
- package/src/boot/runners/server-mode.ts +121 -0
- package/src/boot/runners/session.ts +641 -0
- package/src/boot/server-token.ts +148 -0
- package/src/boot/stages.ts +167 -0
- package/src/boot/upgrade/apply.ts +94 -0
- package/src/boot/upgrade/index.ts +13 -0
- package/src/boot/upgrade/upgrades.ts +289 -0
- package/src/briefing/compose.ts +150 -0
- package/src/briefing/context-docs.ts +19 -0
- package/src/briefing/contract.ts +717 -0
- package/src/briefing/index.ts +31 -0
- package/src/briefing/macros.ts +97 -0
- package/src/briefing/skills.ts +47 -0
- package/src/capability-deck/bridge-ledger/index.ts +27 -0
- package/src/capability-deck/bridge-ledger/key.ts +67 -0
- package/src/capability-deck/bridge-ledger/ledger.ts +131 -0
- package/src/capability-deck/bridge-ledger/network.ts +117 -0
- package/src/capability-deck/builtin-bridge.ts +312 -0
- package/src/capability-deck/cards/bg-process-card.ts +335 -0
- package/src/capability-deck/cards/index.ts +115 -0
- package/src/capability-deck/cards/memory-card.ts +146 -0
- package/src/capability-deck/cards/plan-file.ts +97 -0
- package/src/capability-deck/cards/plan-tools.ts +185 -0
- package/src/capability-deck/cards/saas-card.ts +183 -0
- package/src/capability-deck/cards/task-card.ts +207 -0
- package/src/capability-deck/cards/todo-card.ts +168 -0
- package/src/capability-deck/cards/workflow-card.ts +247 -0
- package/src/capability-deck/contract.ts +388 -0
- package/src/capability-deck/index.ts +48 -0
- package/src/capability-deck/manifest.ts +109 -0
- package/src/capability-deck/provision.ts +169 -0
- package/src/channels/contract.ts +191 -0
- package/src/channels/framer.ts +50 -0
- package/src/channels/index.ts +101 -0
- package/src/channels/link/dialog.ts +129 -0
- package/src/channels/link/driver.ts +190 -0
- package/src/channels/link/index.ts +34 -0
- package/src/channels/link/server.ts +134 -0
- package/src/channels/oneshot.ts +90 -0
- package/src/channels/ops.ts +65 -0
- package/src/channels/session-ops.ts +81 -0
- package/src/conductor/bash-guard.ts +599 -0
- package/src/conductor/catalog/catalog.ts +116 -0
- package/src/conductor/catalog/index.ts +8 -0
- package/src/conductor/catalog/matcher.ts +134 -0
- package/src/conductor/conductor.ts +234 -0
- package/src/conductor/contract.ts +842 -0
- package/src/conductor/diagnostics.ts +227 -0
- package/src/conductor/index.ts +33 -0
- package/src/conductor/permissions.ts +588 -0
- package/src/conductor/quota-error.ts +49 -0
- package/src/conductor/signal-hub/hub.ts +46 -0
- package/src/conductor/signal-hub/index.ts +2 -0
- package/src/conductor/signal-hub/translate.test.ts +81 -0
- package/src/conductor/signal-hub/translate.ts +74 -0
- package/src/conductor/skill-parse/index.ts +2 -0
- package/src/conductor/skill-parse/parse.ts +108 -0
- package/src/conductor/transcript-store/index.ts +22 -0
- package/src/conductor/transcript-store/serialize.ts +116 -0
- package/src/conductor/transcript-store/store.ts +205 -0
- package/src/console/auth-status.ts +56 -0
- package/src/console/components/AgentsView.ts +165 -0
- package/src/console/components/BackgroundAgents.ts +155 -0
- package/src/console/components/Banner.ts +334 -0
- package/src/console/components/Composer.ts +94 -0
- package/src/console/components/StatusBar.ts +49 -0
- package/src/console/components/TerminalConsole.ts +1090 -0
- package/src/console/components/WorkingIndicator.ts +98 -0
- package/src/console/components/banner-sweep.ts +24 -0
- package/src/console/components/welcome.ts +74 -0
- package/src/console/contract.ts +630 -0
- package/src/console/index.ts +34 -0
- package/src/console/input/complete.ts +127 -0
- package/src/console/input/dir-reader.ts +34 -0
- package/src/console/input/index.ts +23 -0
- package/src/console/input/keymap.ts +159 -0
- package/src/console/input/paste.ts +104 -0
- package/src/console/mount.ts +56 -0
- package/src/console/overlays/approval-queue.ts +57 -0
- package/src/console/overlays/approval.ts +130 -0
- package/src/console/overlays/auth.ts +342 -0
- package/src/console/overlays/boards.ts +308 -0
- package/src/console/overlays/host.ts +36 -0
- package/src/console/overlays/index.ts +26 -0
- package/src/console/overlays/pickers.ts +258 -0
- package/src/console/overlays/sessions.ts +190 -0
- package/src/console/reducer.ts +182 -0
- package/src/console/slash/builtins.ts +81 -0
- package/src/console/slash/commands/dynamic.ts +83 -0
- package/src/console/slash/commands/integrations.ts +695 -0
- package/src/console/slash/commands/shared.ts +75 -0
- package/src/console/slash/commands/transcript.ts +263 -0
- package/src/console/slash/commands/workbench.ts +246 -0
- package/src/console/slash/index.ts +15 -0
- package/src/console/slash/registry.ts +70 -0
- package/src/console/slash/resolve.ts +63 -0
- package/src/console/startup.ts +209 -0
- package/src/console/theme/adapter.ts +45 -0
- package/src/console/theme/index.ts +7 -0
- package/src/console/theme/palette.ts +68 -0
- package/src/console/theme/resolve.ts +39 -0
- package/src/console/theme/tokens.ts +71 -0
- package/src/entry.ts +55 -0
- package/src/guardrails.ts +37 -0
- package/src/index.ts +18 -0
- package/src/insight/channel.ts +88 -0
- package/src/insight/contract.ts +185 -0
- package/src/insight/index.ts +110 -0
- package/src/insight/recorder.ts +213 -0
- package/src/insight/redaction.ts +157 -0
- package/src/insight/replay.ts +158 -0
- package/src/insight/sampling.ts +70 -0
- package/src/insight/serialize.ts +50 -0
- package/src/insight/sinks/console.ts +64 -0
- package/src/insight/sinks/file.ts +40 -0
- package/src/insight/sinks/index.ts +24 -0
- package/src/insight/sinks/stream.ts +54 -0
- package/src/integrations/sarvam/attach.ts +239 -0
- package/src/integrations/sarvam/config.ts +156 -0
- package/src/integrations/sarvam/index.ts +25 -0
- package/src/integrations/sarvam/sarvam.test.ts +60 -0
- package/src/integrations/sarvam/types.ts +27 -0
- package/src/integrations/zoho/attach.ts +342 -0
- package/src/integrations/zoho/config.ts +125 -0
- package/src/integrations/zoho/index.ts +27 -0
- package/src/integrations/zoho/types.ts +21 -0
- package/src/integrations/zoho/zoho.test.ts +50 -0
- package/src/kit/clipboard-image.ts +107 -0
- package/src/kit/external-editor.ts +48 -0
- package/src/kit/image.ts +59 -0
- package/src/kit/index.ts +51 -0
- package/src/kit/shell.ts +19 -0
- package/src/kit/tool-fetch.ts +85 -0
- package/src/launch/catalog.ts +148 -0
- package/src/launch/contract.ts +187 -0
- package/src/launch/credentials.ts +625 -0
- package/src/launch/index.ts +98 -0
- package/src/launch/invocation/attachments.ts +179 -0
- package/src/launch/invocation/flags.ts +196 -0
- package/src/launch/invocation/index.ts +25 -0
- package/src/launch/invocation/read.ts +260 -0
- package/src/launch/invocation/usage.ts +67 -0
- package/src/launch/login.ts +324 -0
- package/src/launch/oauth.test.ts +18 -0
- package/src/launch/oauth.ts +203 -0
- package/src/launch/packages.ts +194 -0
- package/src/launch/pickers.ts +189 -0
- package/src/runtime-bridge/bridges/_drive.ts +96 -0
- package/src/runtime-bridge/bridges/builtins.ts +68 -0
- package/src/runtime-bridge/bridges/claude-cli.ts +123 -0
- package/src/runtime-bridge/bridges/codex-cli.ts +142 -0
- package/src/runtime-bridge/bridges/index.ts +33 -0
- package/src/runtime-bridge/bridges/indusagi-cli.ts +155 -0
- package/src/runtime-bridge/broker.ts +227 -0
- package/src/runtime-bridge/contract.ts +122 -0
- package/src/runtime-bridge/index.ts +79 -0
- package/src/runtime-bridge/sink.ts +180 -0
- package/src/sessions/contract.ts +81 -0
- package/src/sessions/index.ts +13 -0
- package/src/sessions/library.ts +229 -0
- package/src/settings/contract.ts +114 -0
- package/src/settings/index.ts +32 -0
- package/src/settings/manager.ts +117 -0
- package/src/transcript-export/index.ts +45 -0
- package/src/transcript-export/publish.ts +260 -0
- package/src/transcript-export/sgr.ts +315 -0
- package/src/transcript-export/template.ts +272 -0
- package/src/transcript-export/theme-bridge.ts +150 -0
- package/src/window-budget/budget/estimate.ts +135 -0
- package/src/window-budget/budget/gate.ts +33 -0
- package/src/window-budget/budget/index.ts +16 -0
- package/src/window-budget/budget/slice.ts +56 -0
- package/src/window-budget/condenser.ts +58 -0
- package/src/window-budget/contract.ts +184 -0
- package/src/window-budget/index.ts +19 -0
- package/src/window-budget/microcompact.ts +95 -0
- package/src/window-budget/rehydrate.ts +136 -0
- package/src/window-budget/summarize/condense.ts +103 -0
- package/src/window-budget/summarize/index.ts +14 -0
- package/src/window-budget/summarize/prompt.ts +149 -0
- package/src/workflow-engine/agent-runner.ts +181 -0
- package/src/workflow-engine/display.ts +224 -0
- package/src/workflow-engine/engine.ts +294 -0
- package/src/workflow-engine/index.ts +23 -0
- package/src/workflow-engine/parse.ts +172 -0
- package/src/workflow-engine/structured-output.ts +35 -0
- package/src/workspace/brand.ts +29 -0
- package/src/workspace/index.ts +18 -0
- package/src/workspace/locator.ts +103 -0
- package/src/workspace/runtime-detect.ts +64 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability cards — the app-novel capabilities the deck builds in-house.
|
|
3
|
+
*
|
|
4
|
+
* Each card is a {@link CapabilityCard}: metadata plus a `build(ctx)` factory
|
|
5
|
+
* that mints a live {@link Capability} (the framework `AgentTool` shape) for a
|
|
6
|
+
* working context. These are written fresh against the framework contract — not
|
|
7
|
+
* derived from any prior tooling layer — and stay framework-agnostic where the
|
|
8
|
+
* behavior is original (todo and bg-process wrap only Node + the deck contract).
|
|
9
|
+
*
|
|
10
|
+
* The connector and memory cards expose a thin, clearly-typed adapter seam over
|
|
11
|
+
* a framework handle injected through {@link DeckContext.framework}; when no
|
|
12
|
+
* handle is wired they degrade to a typed stub so every card builds and runs in
|
|
13
|
+
* any environment, including tests.
|
|
14
|
+
*
|
|
15
|
+
* {@link APP_NOVEL_CARDS} is the contribution this module makes to the catalog;
|
|
16
|
+
* the manifest module composes it with the builtin-bridge cards (the framework's
|
|
17
|
+
* file/shell/search/web tools) to form the single `CAPABILITY_CARDS` source of
|
|
18
|
+
* truth from which the deck's index and profiles are derived.
|
|
19
|
+
*/
|
|
20
|
+
import type { CapabilityCard } from "../contract.js";
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
todoCard,
|
|
24
|
+
buildTodoCapability,
|
|
25
|
+
TodoLedger,
|
|
26
|
+
type TodoItem,
|
|
27
|
+
type TodoState,
|
|
28
|
+
type TodoWeight,
|
|
29
|
+
type TodoParamsType,
|
|
30
|
+
type TodoDetails,
|
|
31
|
+
} from "./todo-card.js";
|
|
32
|
+
export {
|
|
33
|
+
daemonCard,
|
|
34
|
+
buildDaemonCapability,
|
|
35
|
+
DaemonTable,
|
|
36
|
+
type DaemonState,
|
|
37
|
+
type DaemonParamsType,
|
|
38
|
+
type DaemonDetails,
|
|
39
|
+
} from "./bg-process-card.js";
|
|
40
|
+
export {
|
|
41
|
+
taskCard,
|
|
42
|
+
buildTaskCapability,
|
|
43
|
+
DELEGATE_HANDLE_KEY,
|
|
44
|
+
type DelegateRunner,
|
|
45
|
+
type DelegateRequest,
|
|
46
|
+
type DelegateResult,
|
|
47
|
+
type TaskParamsType,
|
|
48
|
+
type TaskDetails,
|
|
49
|
+
} from "./task-card.js";
|
|
50
|
+
export {
|
|
51
|
+
workflowCard,
|
|
52
|
+
buildWorkflowCapability,
|
|
53
|
+
WORKFLOW_HANDLE_KEY,
|
|
54
|
+
type WorkflowParamsType,
|
|
55
|
+
type WorkflowDetails,
|
|
56
|
+
} from "./workflow-card.js";
|
|
57
|
+
export {
|
|
58
|
+
saasCard,
|
|
59
|
+
buildSaasCapability,
|
|
60
|
+
SAAS_GATEWAY_KEY,
|
|
61
|
+
type SaasGatewayPort,
|
|
62
|
+
type RemoteToolSummary,
|
|
63
|
+
type RemoteExecution,
|
|
64
|
+
type SaasParamsType,
|
|
65
|
+
type SaasDetails,
|
|
66
|
+
} from "./saas-card.js";
|
|
67
|
+
export {
|
|
68
|
+
memoryCard,
|
|
69
|
+
buildMemoryCapability,
|
|
70
|
+
InMemoryStore,
|
|
71
|
+
MEMORY_HANDLE_KEY,
|
|
72
|
+
type MemoryStore,
|
|
73
|
+
type MemoryParamsType,
|
|
74
|
+
type MemoryDetails,
|
|
75
|
+
} from "./memory-card.js";
|
|
76
|
+
export {
|
|
77
|
+
enterPlanModeCard,
|
|
78
|
+
exitPlanModeCard,
|
|
79
|
+
buildEnterPlanModeCapability,
|
|
80
|
+
buildExitPlanModeCapability,
|
|
81
|
+
PLAN_HANDLE_KEY,
|
|
82
|
+
type PlanController,
|
|
83
|
+
type EnterPlanParamsType,
|
|
84
|
+
type EnterPlanDetails,
|
|
85
|
+
type ExitPlanParamsType,
|
|
86
|
+
type ExitPlanDetails,
|
|
87
|
+
} from "./plan-tools.js";
|
|
88
|
+
export { planSlug, planFilePath, writePlan, readPlan, PLANS_DIRNAME } from "./plan-file.js";
|
|
89
|
+
|
|
90
|
+
import { todoCard } from "./todo-card.js";
|
|
91
|
+
import { daemonCard } from "./bg-process-card.js";
|
|
92
|
+
import { taskCard } from "./task-card.js";
|
|
93
|
+
import { workflowCard } from "./workflow-card.js";
|
|
94
|
+
import { saasCard } from "./saas-card.js";
|
|
95
|
+
import { memoryCard } from "./memory-card.js";
|
|
96
|
+
import { enterPlanModeCard, exitPlanModeCard } from "./plan-tools.js";
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The app-novel cards, in catalog order. The manifest module concatenates these
|
|
100
|
+
* with the builtin-bridge cards to build the full `CAPABILITY_CARDS` array.
|
|
101
|
+
*
|
|
102
|
+
* The plan-mode tools live here (the broadest `all` profile only): plan mode is a
|
|
103
|
+
* gate over mutating work, so the tools are only meaningful in a full-access deck —
|
|
104
|
+
* never the read-only `authoring` subset.
|
|
105
|
+
*/
|
|
106
|
+
export const APP_NOVEL_CARDS: readonly CapabilityCard[] = [
|
|
107
|
+
todoCard,
|
|
108
|
+
daemonCard,
|
|
109
|
+
taskCard,
|
|
110
|
+
workflowCard,
|
|
111
|
+
saasCard,
|
|
112
|
+
memoryCard,
|
|
113
|
+
enterPlanModeCard,
|
|
114
|
+
exitPlanModeCard,
|
|
115
|
+
];
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Working-memory capability — read and update a persistent scratch note the
|
|
3
|
+
* agent carries across turns.
|
|
4
|
+
*
|
|
5
|
+
* Status: minimal in-memory implementation + clearly-typed seam for the
|
|
6
|
+
* framework memory subsystem.
|
|
7
|
+
*
|
|
8
|
+
* This card ships a self-contained, framework-agnostic default: a single mutable
|
|
9
|
+
* text buffer the agent overwrites or appends to, scoped to one built capability
|
|
10
|
+
* (one session). A host that wants persistence injects a durable store under
|
|
11
|
+
* {@link MEMORY_HANDLE_KEY} via {@link DeckContext.framework} — the boot layer's
|
|
12
|
+
* `DiskMemoryStore` (`boot/runners/memdir.ts`) does exactly this, persisting the
|
|
13
|
+
* note to a per-cwd `MEMORY.md` so it survives across sessions. The
|
|
14
|
+
* {@link Capability} surface and the tool's wire contract do not change either
|
|
15
|
+
* way; {@link readStore} validates the injected store's three methods and falls
|
|
16
|
+
* back to {@link InMemoryStore} when none is wired.
|
|
17
|
+
*
|
|
18
|
+
* The single tool keys behavior on an `action` discriminant:
|
|
19
|
+
* - `read` — return the current working-memory note.
|
|
20
|
+
* - `replace` — overwrite the note with the supplied `content`.
|
|
21
|
+
* - `append` — add a line to the end of the note.
|
|
22
|
+
*/
|
|
23
|
+
import { Type, type Static } from "@sinclair/typebox";
|
|
24
|
+
import { capabilityId, type Capability, type CapabilityCard, type DeckContext } from "../contract.js";
|
|
25
|
+
|
|
26
|
+
/** Key under which a host may wire a framework-backed memory store in later. */
|
|
27
|
+
export const MEMORY_HANDLE_KEY = "memoryStore" as const;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The narrow port the memory capability binds to.
|
|
31
|
+
*
|
|
32
|
+
* The in-memory default below satisfies it; a future framework-backed store can
|
|
33
|
+
* be adapted to the same three methods and injected through the context.
|
|
34
|
+
*/
|
|
35
|
+
export interface MemoryStore {
|
|
36
|
+
read(): string;
|
|
37
|
+
replace(content: string): void;
|
|
38
|
+
append(line: string): void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A trivial in-process working-memory buffer, one per built capability. */
|
|
42
|
+
export class InMemoryStore implements MemoryStore {
|
|
43
|
+
private buffer = "";
|
|
44
|
+
|
|
45
|
+
read(): string {
|
|
46
|
+
return this.buffer;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
replace(content: string): void {
|
|
50
|
+
this.buffer = content;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
append(line: string): void {
|
|
54
|
+
this.buffer = this.buffer.length === 0 ? line : `${this.buffer}\n${line}`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const MemoryParams = Type.Object(
|
|
59
|
+
{
|
|
60
|
+
action: Type.Union(
|
|
61
|
+
[Type.Literal("read"), Type.Literal("replace"), Type.Literal("append")],
|
|
62
|
+
{
|
|
63
|
+
description:
|
|
64
|
+
"`read` returns the note; `replace` overwrites it; `append` adds a line to the end.",
|
|
65
|
+
},
|
|
66
|
+
),
|
|
67
|
+
content: Type.Optional(
|
|
68
|
+
Type.String({
|
|
69
|
+
description:
|
|
70
|
+
"New note body (for `replace`) or the line to add (for `append`). Ignored for `read`.",
|
|
71
|
+
}),
|
|
72
|
+
),
|
|
73
|
+
},
|
|
74
|
+
{ additionalProperties: false },
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
/** Statically-inferred parameter type the capability's `execute` receives. */
|
|
78
|
+
export type MemoryParamsType = Static<typeof MemoryParams>;
|
|
79
|
+
|
|
80
|
+
/** Structured detail returned alongside the model-facing content. */
|
|
81
|
+
export interface MemoryDetails {
|
|
82
|
+
readonly action: "read" | "replace" | "append";
|
|
83
|
+
readonly ok: boolean;
|
|
84
|
+
/** Length of the note after the call, for quick status. */
|
|
85
|
+
readonly length: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const MEMORY_DESCRIPTION =
|
|
89
|
+
'Keep a small working-memory note that persists across turns \u2014 durable facts, decisions, or reminders you want to retain even after older messages scroll out of context. Use `action:"read"` to recall it, `action:"replace"` to rewrite it from scratch, and `action:"append"` to add a single line. Keep it concise; it is a scratchpad, not a log.';
|
|
90
|
+
|
|
91
|
+
/** Read the optional memory store handle from the deck context, falling back to in-memory. */
|
|
92
|
+
function readStore(ctx: DeckContext): MemoryStore {
|
|
93
|
+
const handle = ctx.framework?.[MEMORY_HANDLE_KEY];
|
|
94
|
+
if (
|
|
95
|
+
handle &&
|
|
96
|
+
typeof (handle as MemoryStore).read === "function" &&
|
|
97
|
+
typeof (handle as MemoryStore).replace === "function" &&
|
|
98
|
+
typeof (handle as MemoryStore).append === "function"
|
|
99
|
+
) {
|
|
100
|
+
return handle as MemoryStore;
|
|
101
|
+
}
|
|
102
|
+
return new InMemoryStore();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Build the working-memory capability, binding it to an injected store when one
|
|
107
|
+
* is wired into the context, or a fresh in-memory store otherwise.
|
|
108
|
+
*
|
|
109
|
+
* @param ctx the deck context; an optional store is read from
|
|
110
|
+
* `ctx.framework[MEMORY_HANDLE_KEY]`.
|
|
111
|
+
*/
|
|
112
|
+
export function buildMemoryCapability(ctx: DeckContext): Capability<typeof MemoryParams, MemoryDetails> {
|
|
113
|
+
const store = readStore(ctx);
|
|
114
|
+
return {
|
|
115
|
+
name: "memory",
|
|
116
|
+
label: "Working memory",
|
|
117
|
+
description: MEMORY_DESCRIPTION,
|
|
118
|
+
parameters: MemoryParams,
|
|
119
|
+
async execute(_toolCallId, params) {
|
|
120
|
+
if (params.action === "replace") store.replace(params.content ?? "");
|
|
121
|
+
else if (params.action === "append" && params.content) store.append(params.content);
|
|
122
|
+
const note = store.read();
|
|
123
|
+
const heading =
|
|
124
|
+
params.action === "read"
|
|
125
|
+
? "Working memory:"
|
|
126
|
+
: params.action === "replace"
|
|
127
|
+
? "Working memory replaced:"
|
|
128
|
+
: "Working memory appended:";
|
|
129
|
+
const body = note.length === 0 ? "(empty)" : note;
|
|
130
|
+
return {
|
|
131
|
+
content: [{ type: "text", text: `${heading}\n${body}` }],
|
|
132
|
+
details: { action: params.action, ok: true, length: note.length },
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Catalog row for the working-memory capability. */
|
|
139
|
+
export const memoryCard: CapabilityCard = {
|
|
140
|
+
id: capabilityId("memory"),
|
|
141
|
+
title: "Working memory",
|
|
142
|
+
summary: "Read and update a persistent scratch note that survives across turns.",
|
|
143
|
+
// Erase the precisely-typed builder to the `Capability` card surface (through
|
|
144
|
+
// `unknown`, as TypeBox tools are invariant in their parameter schema).
|
|
145
|
+
build: (ctx) => buildMemoryCapability(ctx),
|
|
146
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan-file persistence — write and read the approved plan a plan-mode session
|
|
3
|
+
* produces, as a slug-named markdown file under a per-session plans directory.
|
|
4
|
+
*
|
|
5
|
+
* Plan mode is a read-only "research first, act second" stance: the agent
|
|
6
|
+
* explores under {@link EnterPlanMode}, drafts a plan, and proposes leaving plan
|
|
7
|
+
* mode with {@link ExitPlanMode}. When the user approves the exit, the plan text
|
|
8
|
+
* the model authored is durable — saved here so it survives the session and can
|
|
9
|
+
* be reviewed, diffed, or fed back later. The conductor owns the approval
|
|
10
|
+
* handshake; this module owns only the bytes-on-disk concern.
|
|
11
|
+
*
|
|
12
|
+
* The filename is derived from a stable slug of the plan's first line (its de
|
|
13
|
+
* facto title), prefixed with a short timestamp so successive plans in one
|
|
14
|
+
* session never collide. Writing is best-effort from the caller's perspective —
|
|
15
|
+
* a write failure surfaces as a thrown error the conductor swallows into a
|
|
16
|
+
* non-fatal note rather than faulting the turn.
|
|
17
|
+
*/
|
|
18
|
+
import { mkdirSync, writeFileSync, readFileSync } from "node:fs";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
|
|
21
|
+
/** The directory plans are written under (relative to the session scope dir). */
|
|
22
|
+
export const PLANS_DIRNAME = "plans" as const;
|
|
23
|
+
|
|
24
|
+
/** Maximum slug length before truncation. */
|
|
25
|
+
const MAX_SLUG_LEN = 48;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Derive a filesystem-safe slug from a plan's text — its first non-empty line,
|
|
29
|
+
* lower-cased, with every run of non-alphanumeric characters collapsed to a
|
|
30
|
+
* single dash and the result trimmed and length-capped. Falls back to `"plan"`
|
|
31
|
+
* when the text yields nothing usable (empty, or punctuation-only).
|
|
32
|
+
*
|
|
33
|
+
* @param plan the plan body whose first line seeds the slug
|
|
34
|
+
*/
|
|
35
|
+
export function planSlug(plan: string): string {
|
|
36
|
+
const firstLine = plan
|
|
37
|
+
.split("\n")
|
|
38
|
+
.map((l) => l.trim())
|
|
39
|
+
.find((l) => l.length > 0);
|
|
40
|
+
const base = (firstLine ?? "")
|
|
41
|
+
.replace(/^#+\s*/, "")
|
|
42
|
+
.toLowerCase()
|
|
43
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
44
|
+
.replace(/^-+|-+$/g, "")
|
|
45
|
+
.slice(0, MAX_SLUG_LEN)
|
|
46
|
+
.replace(/-+$/g, "");
|
|
47
|
+
return base.length > 0 ? base : "plan";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Resolve the absolute path a plan with `slug` is written to under `sessionDir`.
|
|
52
|
+
* The `plans/` sub-directory is implied; callers pass the session scope dir and
|
|
53
|
+
* get back `<sessionDir>/plans/<slug>.md`.
|
|
54
|
+
*
|
|
55
|
+
* @param sessionDir the per-session directory plans live beneath
|
|
56
|
+
* @param slug the derived slug (see {@link planSlug})
|
|
57
|
+
*/
|
|
58
|
+
export function planFilePath(sessionDir: string, slug: string): string {
|
|
59
|
+
return join(sessionDir, PLANS_DIRNAME, `${slug}.md`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Write the plan body to a slug-named markdown file under `sessionDir/plans`,
|
|
64
|
+
* creating the directory as needed, and return the absolute path written. The
|
|
65
|
+
* filename is `<short-timestamp>-<slug>.md` so repeated plans in one session do
|
|
66
|
+
* not overwrite each other.
|
|
67
|
+
*
|
|
68
|
+
* Throws on an I/O failure (the conductor swallows it into a non-fatal note).
|
|
69
|
+
*
|
|
70
|
+
* @param sessionDir the per-session directory plans live beneath
|
|
71
|
+
* @param plan the plan body to persist
|
|
72
|
+
*/
|
|
73
|
+
export function writePlan(sessionDir: string, plan: string): string {
|
|
74
|
+
const dir = join(sessionDir, PLANS_DIRNAME);
|
|
75
|
+
mkdirSync(dir, { recursive: true });
|
|
76
|
+
const stamp = Date.now().toString(36);
|
|
77
|
+
const path = join(dir, `${stamp}-${planSlug(plan)}.md`);
|
|
78
|
+
writeFileSync(path, plan.endsWith("\n") ? plan : `${plan}\n`, {
|
|
79
|
+
encoding: "utf8",
|
|
80
|
+
mode: 0o600,
|
|
81
|
+
});
|
|
82
|
+
return path;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Read a previously-written plan file back, or `undefined` when it is missing or
|
|
87
|
+
* unreadable. Used by tests and by any review surface that wants the saved plan.
|
|
88
|
+
*
|
|
89
|
+
* @param path the absolute plan-file path (see {@link planFilePath} / {@link writePlan})
|
|
90
|
+
*/
|
|
91
|
+
export function readPlan(path: string): string | undefined {
|
|
92
|
+
try {
|
|
93
|
+
return readFileSync(path, "utf8");
|
|
94
|
+
} catch {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan-mode capabilities — the two tools that bracket a read-only planning phase.
|
|
3
|
+
*
|
|
4
|
+
* - {@link enterPlanModeTool} (`enter_plan_mode`) is a **read-only** tool: when
|
|
5
|
+
* invoked it returns a structured detail `{ enterPlanMode: true }` plus
|
|
6
|
+
* explore-only prose. It carries no side effect of its own — the **conductor**
|
|
7
|
+
* watches the tool-result seam for `enterPlanMode` and switches the session
|
|
8
|
+
* into the `plan` permission mode (research-only — every mutating tool is
|
|
9
|
+
* blocked at the permission gate), capturing the pre-plan mode so it can be
|
|
10
|
+
* restored on exit. It is marked `readOnly: true` so it is itself always
|
|
11
|
+
* permitted, even inside plan mode.
|
|
12
|
+
*
|
|
13
|
+
* - {@link exitPlanModeTool} (`exit_plan_mode`) likewise does NOT flip the mode
|
|
14
|
+
* itself — a tool cannot drive the interactive approval dialog. It returns a
|
|
15
|
+
* structured detail `{ exitPlan: true, plan }` that the conductor intercepts:
|
|
16
|
+
* the conductor raises the user-approval prompt, and on approval restores the
|
|
17
|
+
* pre-plan mode + injects the approved plan as context for the next turn,
|
|
18
|
+
* persisting it to a plan file. On reject the session stays in plan mode.
|
|
19
|
+
*
|
|
20
|
+
* Keeping every side effect (mode flip, approval, persistence) in the conductor
|
|
21
|
+
* rather than the tool keeps the tools pure, side-effect-light, and assemblable in
|
|
22
|
+
* any environment, including tests — and lets the conductor own the single
|
|
23
|
+
* permission-mode source of truth.
|
|
24
|
+
*/
|
|
25
|
+
import { Type, type Static } from "@sinclair/typebox";
|
|
26
|
+
import { capabilityId, type Capability, type CapabilityCard, type DeckContext } from "../contract.js";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Reserved key under which a host MAY wire a plan-mode controller into the deck
|
|
30
|
+
* context. The conductor-driven design intercepts the plan tools' result details
|
|
31
|
+
* directly (no handle required), so this is exported only for forward-compat /
|
|
32
|
+
* an alternate host that wants the tool to drive the flip itself.
|
|
33
|
+
*/
|
|
34
|
+
export const PLAN_HANDLE_KEY = "planController" as const;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The narrow port a host may wire to flip the live session into plan mode. Unused
|
|
38
|
+
* by the default (conductor-intercept) wiring; kept for hosts that prefer the tool
|
|
39
|
+
* to signal directly rather than via the tool-result seam.
|
|
40
|
+
*/
|
|
41
|
+
export interface PlanController {
|
|
42
|
+
/** Switch the session into read-only plan mode for subsequent tool calls. */
|
|
43
|
+
enterPlanMode(): void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const EnterPlanParams = Type.Object({}, { additionalProperties: false });
|
|
47
|
+
|
|
48
|
+
/** Statically-inferred parameter type EnterPlanMode's `execute` receives. */
|
|
49
|
+
export type EnterPlanParamsType = Static<typeof EnterPlanParams>;
|
|
50
|
+
|
|
51
|
+
/** Structured detail returned by EnterPlanMode. */
|
|
52
|
+
export interface EnterPlanDetails {
|
|
53
|
+
/**
|
|
54
|
+
* The marker the conductor intercepts on the tool-result seam to switch the
|
|
55
|
+
* session into plan mode (and capture the pre-plan mode for a later restore).
|
|
56
|
+
*/
|
|
57
|
+
readonly enterPlanMode: true;
|
|
58
|
+
/** Whether an optional in-context controller also applied the switch directly. */
|
|
59
|
+
readonly applied: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const ExitPlanParams = Type.Object(
|
|
63
|
+
{
|
|
64
|
+
plan: Type.String({
|
|
65
|
+
description:
|
|
66
|
+
"The plan you intend to carry out, in markdown. The user reviews this before approving the exit from plan mode; on approval you leave plan mode and begin.",
|
|
67
|
+
}),
|
|
68
|
+
},
|
|
69
|
+
{ additionalProperties: false },
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
/** Statically-inferred parameter type ExitPlanMode's `execute` receives. */
|
|
73
|
+
export type ExitPlanParamsType = Static<typeof ExitPlanParams>;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Structured detail returned by ExitPlanMode. The conductor watches the
|
|
77
|
+
* tool-result seam for `exitPlan === true`, captures the {@link plan}, and runs
|
|
78
|
+
* the approval handshake (the tool itself does not flip the mode or prompt).
|
|
79
|
+
*/
|
|
80
|
+
export interface ExitPlanDetails {
|
|
81
|
+
/** The marker the conductor intercepts to start the exit-plan handshake. */
|
|
82
|
+
readonly exitPlan: true;
|
|
83
|
+
/** The plan text the model authored, surfaced to the user for approval. */
|
|
84
|
+
readonly plan: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const ENTER_PLAN_DESCRIPTION =
|
|
88
|
+
"Enter plan mode: a read-only research phase in which you may only read, search, and inspect \u2014 every file-mutating and shell tool is blocked until you leave. Use this when a task needs investigation and a plan before any change is made. While in plan mode, gather all the context you need, then call `exit_plan_mode` with the plan you intend to carry out; the user reviews and approves it before you act.";
|
|
89
|
+
|
|
90
|
+
const ENTER_PLAN_PROSE =
|
|
91
|
+
"You are now in plan mode (read-only). Investigate freely with the read, search, and listing tools, but do not attempt to edit files or run shell commands \u2014 they are blocked here. When you have a concrete plan, call `exit_plan_mode` with the full plan text so the user can approve it before you make any changes.";
|
|
92
|
+
|
|
93
|
+
const EXIT_PLAN_DESCRIPTION =
|
|
94
|
+
"Leave plan mode and propose the plan you intend to carry out. Provide the full plan as `plan`; the user reviews it and either approves (you exit plan mode and begin the work) or rejects (you stay in plan mode to revise). Only call this once you have a concrete, reviewed plan \u2014 not to do work, but to ask permission to start it.";
|
|
95
|
+
|
|
96
|
+
/** Read the optional plan controller handle from the deck context. */
|
|
97
|
+
function readPlanController(ctx: DeckContext): PlanController | undefined {
|
|
98
|
+
const handle = ctx.framework?.[PLAN_HANDLE_KEY];
|
|
99
|
+
if (handle && typeof (handle as PlanController).enterPlanMode === "function") {
|
|
100
|
+
return handle as PlanController;
|
|
101
|
+
}
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Build the EnterPlanMode capability. On invoke it asks the wired
|
|
107
|
+
* {@link PlanController} to switch the session into plan mode, then returns
|
|
108
|
+
* explore-only prose. It is `readOnly: true`, so the permission gate always
|
|
109
|
+
* permits it (including inside plan mode itself).
|
|
110
|
+
*
|
|
111
|
+
* @param ctx the deck context; an optional controller is read from
|
|
112
|
+
* `ctx.framework[PLAN_HANDLE_KEY]`.
|
|
113
|
+
*/
|
|
114
|
+
export function buildEnterPlanModeCapability(
|
|
115
|
+
ctx: DeckContext,
|
|
116
|
+
): Capability<typeof EnterPlanParams, EnterPlanDetails> {
|
|
117
|
+
const controller = readPlanController(ctx);
|
|
118
|
+
return {
|
|
119
|
+
name: "enter_plan_mode",
|
|
120
|
+
label: "Enter plan mode",
|
|
121
|
+
description: ENTER_PLAN_DESCRIPTION,
|
|
122
|
+
readOnly: true,
|
|
123
|
+
parameters: EnterPlanParams,
|
|
124
|
+
async execute() {
|
|
125
|
+
const applied = controller !== undefined;
|
|
126
|
+
if (controller) controller.enterPlanMode();
|
|
127
|
+
return {
|
|
128
|
+
content: [{ type: "text", text: ENTER_PLAN_PROSE }],
|
|
129
|
+
details: { enterPlanMode: true, applied },
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Build the ExitPlanMode capability. It performs no mode flip and no prompt of
|
|
137
|
+
* its own — it returns the `{ exitPlan: true, plan }` detail the conductor
|
|
138
|
+
* intercepts. The model-facing content echoes the proposed plan so a transcript
|
|
139
|
+
* reader sees what was put up for approval.
|
|
140
|
+
*
|
|
141
|
+
* Not marked read-only: it represents the request to RESUME mutating work, so in
|
|
142
|
+
* plan mode it must still be permitted explicitly (the gate special-cases the
|
|
143
|
+
* plan tools alongside read-only tools — see the conductor wiring).
|
|
144
|
+
*/
|
|
145
|
+
export function buildExitPlanModeCapability(
|
|
146
|
+
_ctx: DeckContext,
|
|
147
|
+
): Capability<typeof ExitPlanParams, ExitPlanDetails> {
|
|
148
|
+
return {
|
|
149
|
+
name: "exit_plan_mode",
|
|
150
|
+
label: "Exit plan mode",
|
|
151
|
+
description: EXIT_PLAN_DESCRIPTION,
|
|
152
|
+
// Marked read-only so the plan-mode gate never blocks the very tool used to
|
|
153
|
+
// request leaving plan mode (it mutates nothing — the conductor owns the flip).
|
|
154
|
+
readOnly: true,
|
|
155
|
+
parameters: ExitPlanParams,
|
|
156
|
+
async execute(_toolCallId, params) {
|
|
157
|
+
const plan = params.plan;
|
|
158
|
+
return {
|
|
159
|
+
content: [
|
|
160
|
+
{
|
|
161
|
+
type: "text",
|
|
162
|
+
text: `Proposed plan (awaiting your approval to leave plan mode):\n\n${plan}`,
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
details: { exitPlan: true, plan },
|
|
166
|
+
};
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Catalog row for the EnterPlanMode capability. */
|
|
172
|
+
export const enterPlanModeCard: CapabilityCard = {
|
|
173
|
+
id: capabilityId("enter_plan_mode"),
|
|
174
|
+
title: "Enter plan mode",
|
|
175
|
+
summary: "Switch into a read-only research phase where mutating tools are blocked.",
|
|
176
|
+
build: (ctx) => buildEnterPlanModeCapability(ctx),
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/** Catalog row for the ExitPlanMode capability. */
|
|
180
|
+
export const exitPlanModeCard: CapabilityCard = {
|
|
181
|
+
id: capabilityId("exit_plan_mode"),
|
|
182
|
+
title: "Exit plan mode",
|
|
183
|
+
summary: "Propose a plan and request approval to leave plan mode and begin work.",
|
|
184
|
+
build: (ctx) => buildExitPlanModeCapability(ctx),
|
|
185
|
+
};
|