indusagi-coding-agent 0.1.62 → 0.2.0
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/dist/entry.js +5395 -1509
- package/dist/types/boot/contract.d.ts +2 -0
- package/dist/types/boot/runners/addon-wiring.d.ts +103 -0
- package/dist/types/boot/runners/addon-wiring.test.d.ts +19 -0
- package/dist/types/boot/runners/checkpoint.d.ts +133 -0
- package/dist/types/boot/runners/checkpoint.test.d.ts +12 -0
- package/dist/types/boot/runners/delegate-runner.d.ts +83 -0
- package/dist/types/boot/runners/delegate-runner.test.d.ts +13 -0
- package/dist/types/boot/runners/memdir.d.ts +103 -0
- package/dist/types/boot/runners/memdir.test.d.ts +12 -0
- package/dist/types/boot/runners/read-state.d.ts +82 -0
- package/dist/types/boot/runners/read-state.test.d.ts +10 -0
- package/dist/types/boot/runners/session.d.ts +37 -2
- package/dist/types/boot/runners/session.test.d.ts +10 -0
- package/dist/types/briefing/context-docs.d.ts +38 -0
- package/dist/types/briefing/context-docs.test.d.ts +18 -0
- package/dist/types/briefing/index.d.ts +2 -0
- package/dist/types/capability-deck/cards/index.d.ts +6 -0
- package/dist/types/capability-deck/cards/memory-card.d.ts +9 -10
- package/dist/types/capability-deck/cards/plan-file.d.ts +56 -0
- package/dist/types/capability-deck/cards/plan-tools.d.ts +97 -0
- package/dist/types/capability-deck/cards/plan-tools.test.d.ts +9 -0
- package/dist/types/capability-deck/checkpoint.int.test.d.ts +25 -0
- package/dist/types/capability-deck/index.d.ts +1 -1
- package/dist/types/capability-deck/read-edit-gate.int.test.d.ts +21 -0
- package/dist/types/conductor/bash-guard.d.ts +106 -0
- package/dist/types/conductor/bash-guard.test.d.ts +17 -0
- package/dist/types/conductor/conductor.d.ts +37 -6
- package/dist/types/conductor/contract.d.ts +214 -2
- package/dist/types/conductor/diagnostics.d.ts +183 -0
- package/dist/types/conductor/diagnostics.test.d.ts +10 -0
- package/dist/types/conductor/index.d.ts +4 -1
- package/dist/types/conductor/permission-gate.integration.test.d.ts +22 -0
- package/dist/types/conductor/permission-wiring.test.d.ts +14 -0
- package/dist/types/conductor/permissions.d.ts +217 -0
- package/dist/types/conductor/permissions.test.d.ts +12 -0
- package/dist/types/conductor/plan-mode.integration.test.d.ts +23 -0
- package/dist/types/conductor/post-edit-diagnostics.test.d.ts +13 -0
- package/dist/types/conductor/transcript-store/serialize.test.d.ts +10 -0
- package/dist/types/conductor/transcript-store/store.d.ts +18 -0
- package/dist/types/console/components/StatusBar.d.ts +14 -3
- package/dist/types/console/components/WorkingIndicator.d.ts +44 -0
- package/dist/types/console/components/WorkingIndicator.test.d.ts +9 -0
- package/dist/types/console/contract.d.ts +2 -1
- package/dist/types/console/input/keymap.d.ts +10 -1
- package/dist/types/console/overlays/approval-queue.d.ts +71 -0
- package/dist/types/console/overlays/approval.d.ts +104 -0
- package/dist/types/console/overlays/approval.test.d.ts +17 -0
- package/dist/types/console/overlays/host.d.ts +4 -3
- package/dist/types/console/overlays/index.d.ts +2 -0
- package/dist/types/launch/contract.d.ts +2 -0
- package/dist/types/launch/index.d.ts +1 -1
- package/dist/types/launch/oauth.d.ts +13 -0
- package/dist/types/settings/contract.d.ts +47 -0
- package/dist/types/settings/index.d.ts +2 -2
- package/dist/types/window-budget/condenser.d.ts +15 -1
- package/dist/types/window-budget/index.d.ts +3 -1
- package/dist/types/window-budget/microcompact.d.ts +68 -0
- package/dist/types/window-budget/microcompact.test.d.ts +16 -0
- package/dist/types/window-budget/rehydrate.d.ts +56 -0
- package/dist/types/workspace/brand.d.ts +1 -1
- package/package.json +2 -2
|
@@ -123,6 +123,8 @@ export interface Invocation {
|
|
|
123
123
|
readonly prompt?: string;
|
|
124
124
|
/** Explicit model selector from the command line, if any (`--model` / `-m`). */
|
|
125
125
|
readonly modelId?: string;
|
|
126
|
+
/** Model to fall back to when the bound model is overloaded mid-turn (`--fallback-model`). */
|
|
127
|
+
readonly fallbackModelId?: string;
|
|
126
128
|
/** Working directory the run is scoped to; absent means the process cwd (`--cwd`). */
|
|
127
129
|
readonly cwd?: string;
|
|
128
130
|
/** Named credential account to authenticate with (`--account`). */
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boot helper: activate the addon host for a session and fold its tool
|
|
3
|
+
* interceptors around the deck.
|
|
4
|
+
*
|
|
5
|
+
* The {@link createAddonHost addon host} is the product's extension mechanism —
|
|
6
|
+
* locally-authored modules under `<cwd>/.indus/addons` that graft tools, slash
|
|
7
|
+
* commands, lifecycle observers, and tool-boundary interceptors onto a running
|
|
8
|
+
* session. The host itself is fully built and tested, but until this module it
|
|
9
|
+
* was never *instantiated* outside its own unit tests: nothing in the boot path
|
|
10
|
+
* discovered, loaded, or wired addons.
|
|
11
|
+
*
|
|
12
|
+
* This module closes that gap with two narrow seams the runners call once at
|
|
13
|
+
* session-assembly time:
|
|
14
|
+
*
|
|
15
|
+
* 1. {@link buildAddonHost} — construct a host, swallow its fault stream (a
|
|
16
|
+
* broken addon must never sink the session), and {@link AddonHost.loadAll
|
|
17
|
+
* load} every addon discovered under the run's `.indus/addons` directory.
|
|
18
|
+
* With no such directory the returned {@link AddonSurfaceBundle} is empty —
|
|
19
|
+
* no interceptors, no contributed tools — so wiring it is a perfect no-op.
|
|
20
|
+
* 2. {@link wrapToolsWithAddons} — fold the bundle's
|
|
21
|
+
* {@link InterceptorChain} around every tool whose name a stage matches,
|
|
22
|
+
* leaving the rest identity-equal. A wrapped tool runs the chain's
|
|
23
|
+
* `enter` → real `execute` → `exit` reduce; an `enter` block short-circuits
|
|
24
|
+
* to an `isError` result without ever invoking the real tool.
|
|
25
|
+
*
|
|
26
|
+
* Scope (v1): only the per-tool interceptor boundary is wired. The richer
|
|
27
|
+
* lifecycle-event fan-out (`session:start`, `turn:end`, …) needs a conductor
|
|
28
|
+
* seam that does not exist yet and is deferred. The tool interceptor boundary is
|
|
29
|
+
* the load-bearing one — it is where the Wave 4 permission gate later registers
|
|
30
|
+
* as a built-in interceptor rather than a parallel mechanism.
|
|
31
|
+
*/
|
|
32
|
+
import { type AddonSurfaceBundle, type AgentTool, type InterceptorChain } from "../../addons";
|
|
33
|
+
import type { BootContext } from "../contract";
|
|
34
|
+
/**
|
|
35
|
+
* Build and populate the addon host for a run, returning its wired
|
|
36
|
+
* {@link AddonSurfaceBundle}.
|
|
37
|
+
*
|
|
38
|
+
* Constructs a host with an empty {@link FrameworkHandles} bag (v1 supplies no
|
|
39
|
+
* `exec` handle — the runner has no shell-exec callback to hand an addon — and
|
|
40
|
+
* the rest are interactive-only), installs a swallow-everything fault sink so a
|
|
41
|
+
* broken addon degrades silently instead of crashing the boot, then discovers
|
|
42
|
+
* and loads every addon under `<cwd>/.indus/addons` (the contract's default
|
|
43
|
+
* {@link ADDONS_DIR}). With no addons directory the bundle is empty and wiring it
|
|
44
|
+
* downstream is a no-op.
|
|
45
|
+
*
|
|
46
|
+
* @param ctx the boot context whose invocation carries the run cwd
|
|
47
|
+
* @returns the wired bundle (dispatch, interceptors, contributed commands/tools)
|
|
48
|
+
*/
|
|
49
|
+
export declare function buildAddonHost(ctx: BootContext): Promise<AddonSurfaceBundle>;
|
|
50
|
+
/**
|
|
51
|
+
* Fold a bundle's {@link InterceptorChain} around every tool a stage matches.
|
|
52
|
+
*
|
|
53
|
+
* Each tool whose `name` the chain matches is replaced with a wrapper that runs
|
|
54
|
+
* the chain around its real `execute`; every other tool is returned *identical*
|
|
55
|
+
* (same object reference) so an empty bundle leaves the deck untouched. The
|
|
56
|
+
* contributed `bundle.tools` are NOT appended here — the caller concatenates
|
|
57
|
+
* them (de-duped against the existing deck) before wrapping, so addon tools are
|
|
58
|
+
* themselves subject to interception.
|
|
59
|
+
*
|
|
60
|
+
* @param tools the run's tool deck (deck + MCP + already-concatenated addon tools)
|
|
61
|
+
* @param bundle the loaded addon bundle whose interceptor chain wraps the deck
|
|
62
|
+
* @returns a new array: matched tools wrapped, unmatched tools identity-equal
|
|
63
|
+
*/
|
|
64
|
+
export declare function wrapToolsWithAddons(tools: AgentTool[], bundle: AddonSurfaceBundle): AgentTool[];
|
|
65
|
+
/**
|
|
66
|
+
* Wrap one tool so its `execute` runs through the interceptor chain.
|
|
67
|
+
*
|
|
68
|
+
* The wrapper spreads the original tool (preserving `name` / `description` /
|
|
69
|
+
* `parameters` / `label` and any extra fields) and overrides only `execute`. The
|
|
70
|
+
* override:
|
|
71
|
+
*
|
|
72
|
+
* - closes over the live `toolCallId`, `signal`, and `onUpdate` so streaming
|
|
73
|
+
* updates and cancellation still reach the real tool (the chain only threads
|
|
74
|
+
* the decoded `args`, so these must be captured here);
|
|
75
|
+
* - hands the chain a `(args) => realExecute(args)` closure as the inner
|
|
76
|
+
* execution and runs `chain.run({ tool, callId, args }, …)`;
|
|
77
|
+
* - maps the resulting {@link InterceptResult} back to an
|
|
78
|
+
* {@link AgentToolResult}: a `blocked` enter short-circuits to an `isError`
|
|
79
|
+
* result carrying the gate's `reason` (the real tool never ran); otherwise
|
|
80
|
+
* the chain's (possibly exit-rewritten) `result` is returned.
|
|
81
|
+
*
|
|
82
|
+
* The chain — not this wrapper — owns fault isolation and the throw-on-no-recover
|
|
83
|
+
* semantics, so a tool error that no exit stage recovers propagates exactly as it
|
|
84
|
+
* would unwrapped.
|
|
85
|
+
*
|
|
86
|
+
* @param tool the real tool to fold the chain around
|
|
87
|
+
* @param chain the interceptor chain matching this tool's name
|
|
88
|
+
*/
|
|
89
|
+
export declare function wrapOne(tool: AgentTool, chain: InterceptorChain): AgentTool;
|
|
90
|
+
/**
|
|
91
|
+
* Concatenate an addon bundle's contributed tools onto an existing deck, dropping
|
|
92
|
+
* any whose name a deck tool already claims.
|
|
93
|
+
*
|
|
94
|
+
* The host de-dupes its OWN tools against each other, but not against the product
|
|
95
|
+
* deck or MCP tools — so an addon tool named `read` would otherwise shadow (or
|
|
96
|
+
* be appended alongside) the core read tool. This filter keeps the first
|
|
97
|
+
* claimant (the existing deck) and admits only addon tools with a fresh name.
|
|
98
|
+
*
|
|
99
|
+
* @param deck the existing tool deck (deck + MCP)
|
|
100
|
+
* @param bundle the loaded addon bundle whose `tools` are appended
|
|
101
|
+
* @returns the deck followed by the non-conflicting addon tools
|
|
102
|
+
*/
|
|
103
|
+
export declare function concatAddonTools(deck: AgentTool[], bundle: AddonSurfaceBundle): AgentTool[];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Addon wiring — boot-time activation of the addon host (item #19).
|
|
3
|
+
*
|
|
4
|
+
* These tests exercise the two seams this module adds to the boot path WITHOUT a
|
|
5
|
+
* real conductor, real jiti, or disk: a fake {@link ModuleLoader} maps a source
|
|
6
|
+
* path to a scripted {@link AddonManifest}, the host loads it through
|
|
7
|
+
* `explicitPaths` (which never touches the filesystem), and the resulting bundle
|
|
8
|
+
* is folded around a synthetic tool deck.
|
|
9
|
+
*
|
|
10
|
+
* Four concerns:
|
|
11
|
+
* 1. An `enter` block short-circuits the call — the wrapper returns an
|
|
12
|
+
* `isError` result carrying the gate reason and the real `execute` NEVER
|
|
13
|
+
* runs.
|
|
14
|
+
* 2. An `exit` rewrite replaces the tool's result.
|
|
15
|
+
* 3. A tool no interceptor matches is returned IDENTITY-equal (the empty-bundle
|
|
16
|
+
* / no-addons path is a perfect no-op).
|
|
17
|
+
* 4. Contributed addon tools are concatenated, de-duped against the deck.
|
|
18
|
+
*/
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-session file-checkpoint store — the product half of rewind (#24).
|
|
3
|
+
*
|
|
4
|
+
* The framework's mutating file tools (write/edit) consult a host-injected handle
|
|
5
|
+
* on `ctx.framework` under the string-literal key {@link CHECKPOINT_HANDLE_KEY}.
|
|
6
|
+
* Immediately before a file is written — and after the read-before-edit gate has
|
|
7
|
+
* passed — the framework reads the file's CURRENT on-disk content and hands it to
|
|
8
|
+
* the handle as `record(absPath, previous)`, where `previous` is the old bytes or
|
|
9
|
+
* `null` when the file did not yet exist. This module supplies the missing host
|
|
10
|
+
* piece: a store that files those pre-mutation snapshots against the transcript
|
|
11
|
+
* node that was active when the mutation happened, so a later rewind to that node
|
|
12
|
+
* can roll the working tree back to exactly that point.
|
|
13
|
+
*
|
|
14
|
+
* Design notes:
|
|
15
|
+
* - **Keyed by the ACTIVE transcript node id.** Each snapshot is filed under the
|
|
16
|
+
* id the conductor pins as the active node (the head leaf at turn start). All
|
|
17
|
+
* of a turn's edits therefore land under ONE node id; navigating back to that
|
|
18
|
+
* node and restoring reverts the whole turn's file changes at once.
|
|
19
|
+
* - **First-seen wins per path within a node.** If the same file is edited twice
|
|
20
|
+
* in one turn, only the FIRST snapshot (the content as it was when the node
|
|
21
|
+
* became active) is kept. Overwriting it would lose the true pre-turn state,
|
|
22
|
+
* so a second `record` of the same path under the same node is ignored. This
|
|
23
|
+
* also makes the framework's pre-write `record` idempotent across the multiple
|
|
24
|
+
* mutations one tool call may perform.
|
|
25
|
+
* - **`null` previous means "file was absent".** Restoring a node whose snapshot
|
|
26
|
+
* marks a file `null` DELETES that file (it did not exist at that point), so a
|
|
27
|
+
* rewind that created a file is undone by removing it.
|
|
28
|
+
* - **Duck-typed handle.** The store implements exactly the framework's
|
|
29
|
+
* `CheckpointHandle` shape (`record(absPath, previous: string | null): void`)
|
|
30
|
+
* so the host injector and the framework consumer agree by STRING KEY and
|
|
31
|
+
* STRUCTURAL SHAPE — no cross-package type import. `ctx.framework` is an open
|
|
32
|
+
* record, so the injection typechecks immediately; the framework consumption
|
|
33
|
+
* activates after the framework is rebuilt and the product refreshed.
|
|
34
|
+
* - **One instance PER SESSION** (minted in `session.ts`), shared across the
|
|
35
|
+
* write/edit cards through the single `ctx.framework` bag, and handed to the
|
|
36
|
+
* conductor so the conductor can advance the active node id as the head moves.
|
|
37
|
+
*
|
|
38
|
+
* Mirrors the read-edit-gate's `ReadStateStore` conventions (`READ_STATE_HANDLE_KEY`,
|
|
39
|
+
* `createReadStateStore`); both coexist in the SAME `ctx.framework` open bag.
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* The string-literal key under which the host wires this store onto the deck's
|
|
43
|
+
* `ctx.framework` bag.
|
|
44
|
+
*
|
|
45
|
+
* Mirrors the existing `DELEGATE_HANDLE_KEY = "delegate"`,
|
|
46
|
+
* `MEMORY_HANDLE_KEY = "memoryStore"`, and `READ_STATE_HANDLE_KEY = "readState"`
|
|
47
|
+
* conventions. The framework's mutating file tools read
|
|
48
|
+
* `ctx.framework["checkpoint"]` by the same literal, so the two sides agree by
|
|
49
|
+
* string rather than by a shared imported symbol.
|
|
50
|
+
*/
|
|
51
|
+
export declare const CHECKPOINT_HANDLE_KEY: "checkpoint";
|
|
52
|
+
/**
|
|
53
|
+
* One file's pre-mutation snapshot: the path that was mutated and the content it
|
|
54
|
+
* held immediately before. `previous === null` records that the file did not
|
|
55
|
+
* exist on disk before the mutation, so restoring this snapshot deletes it.
|
|
56
|
+
*/
|
|
57
|
+
export interface FileSnapshot {
|
|
58
|
+
readonly path: string;
|
|
59
|
+
readonly previous: string | null;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Per-session file-checkpoint store, keyed by transcript node id.
|
|
63
|
+
*
|
|
64
|
+
* For each node id it holds a map of normalized absolute path → the FIRST-seen
|
|
65
|
+
* pre-mutation content for that path under that node. The framework calls
|
|
66
|
+
* {@link record} (via the duck-typed handle) before every write; the host pins
|
|
67
|
+
* the active node id with {@link setActiveNodeId} as the conductor's head moves;
|
|
68
|
+
* and {@link restore} rolls the working tree back to a node's recorded state.
|
|
69
|
+
*/
|
|
70
|
+
export declare class CheckpointStore {
|
|
71
|
+
/** node id → (normalized abs path → first-seen pre-mutation content). */
|
|
72
|
+
private readonly byNode;
|
|
73
|
+
/** The transcript node the next {@link record} files its snapshot under. */
|
|
74
|
+
private current;
|
|
75
|
+
/** Normalize an absolute path into the store's canonical per-node key. */
|
|
76
|
+
private key;
|
|
77
|
+
/** The node id new snapshots are currently filed under (`null` until pinned). */
|
|
78
|
+
activeNodeId(): string | null;
|
|
79
|
+
/**
|
|
80
|
+
* Pin the transcript node subsequent {@link record} calls file snapshots under.
|
|
81
|
+
*
|
|
82
|
+
* The conductor calls this as its head advances (at turn start), so all of a
|
|
83
|
+
* turn's edits key to the node that was active before the turn ran. Passing the
|
|
84
|
+
* same id again is harmless.
|
|
85
|
+
*
|
|
86
|
+
* @param id the active transcript node id, or `null` to fall back to the root
|
|
87
|
+
*/
|
|
88
|
+
setActiveNodeId(id: string | null): void;
|
|
89
|
+
/** The node id snapshots are filed under right now (active id or the root). */
|
|
90
|
+
private resolveNodeId;
|
|
91
|
+
/**
|
|
92
|
+
* Capture the pre-mutation content of `absPath` under the active node.
|
|
93
|
+
*
|
|
94
|
+
* The framework's duck-typed `CheckpointHandle` entry point. Called once per
|
|
95
|
+
* mutated path immediately before the write lands, with the file's OLD on-disk
|
|
96
|
+
* content (or `null` when the file did not exist). First-seen wins: a later
|
|
97
|
+
* `record` of the same path under the same node is ignored so the snapshot
|
|
98
|
+
* reflects the file's state when the node became active, not a mid-turn rewrite.
|
|
99
|
+
*
|
|
100
|
+
* @param absPath absolute path of the file about to be written
|
|
101
|
+
* @param previous the file's content before this mutation, or `null` if absent
|
|
102
|
+
*/
|
|
103
|
+
record(absPath: string, previous: string | null): void;
|
|
104
|
+
/**
|
|
105
|
+
* The recorded snapshots for a node, or `[]` when nothing was tracked there.
|
|
106
|
+
*
|
|
107
|
+
* Each entry pairs the normalized absolute path with the content it held before
|
|
108
|
+
* the node's first mutation of it (`null` = the file was absent).
|
|
109
|
+
*
|
|
110
|
+
* @param nodeId the transcript node to read snapshots for
|
|
111
|
+
*/
|
|
112
|
+
snapshotFor(nodeId: string): readonly FileSnapshot[];
|
|
113
|
+
/** Whether a node has ANY recorded file snapshot (the picker's restore gate). */
|
|
114
|
+
hasSnapshot(nodeId: string): boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Roll the working tree back to a node's recorded state.
|
|
117
|
+
*
|
|
118
|
+
* For each path the node tracked: when its snapshot is a string, the file is
|
|
119
|
+
* rewritten with that pre-mutation content (creating parent directories as
|
|
120
|
+
* needed); when the snapshot is `null` the file is DELETED (it did not exist at
|
|
121
|
+
* that point). Returns the absolute paths that were restored, in record order.
|
|
122
|
+
*
|
|
123
|
+
* A safe no-op when the node has no snapshot — navigating to a node that never
|
|
124
|
+
* mutated files restores nothing. Per-path failures are swallowed so one
|
|
125
|
+
* unwritable path never aborts the rest of the restore.
|
|
126
|
+
*
|
|
127
|
+
* @param nodeId the transcript node whose file state to restore to
|
|
128
|
+
* @returns the absolute paths that were written or deleted
|
|
129
|
+
*/
|
|
130
|
+
restore(nodeId: string): string[];
|
|
131
|
+
}
|
|
132
|
+
/** Mint a fresh, empty per-session checkpoint store. */
|
|
133
|
+
export declare function createCheckpointStore(): CheckpointStore;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CheckpointStore unit tests — the product half of rewind (#24).
|
|
3
|
+
*
|
|
4
|
+
* Cover the store's contract in isolation (no framework tools):
|
|
5
|
+
* - per-node FIRST-SEEN snapshot (a path's snapshot within one node is never
|
|
6
|
+
* overwritten by a later record);
|
|
7
|
+
* - keying follows the active node id (set via `setActiveNodeId`);
|
|
8
|
+
* - `restore` writes the recorded old content back AND deletes files whose
|
|
9
|
+
* snapshot was `null` (absent at that point);
|
|
10
|
+
* - `restore` of an untracked node is a safe no-op.
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boot helper: a live {@link DelegateRunner} that makes the `task` tool real.
|
|
3
|
+
*
|
|
4
|
+
* The `task` capability ({@link "../../capability-deck/cards/task-card"}) advertises
|
|
5
|
+
* sub-agent delegation but only *runs* it when a {@link DelegateRunner} is wired
|
|
6
|
+
* into the deck context under {@link DELEGATE_HANDLE_KEY}; absent one it degrades
|
|
7
|
+
* to a typed `STUB_NOTE`. This module supplies that runner.
|
|
8
|
+
*
|
|
9
|
+
* Each delegated objective spins a *fresh, isolated* framework {@link Agent} (the
|
|
10
|
+
* same `facade/bot` Agent the conductor already drives — NOT the swarm `Crew` nor
|
|
11
|
+
* `runtime/createAgent`) bound to the parent's model id and credential resolver,
|
|
12
|
+
* given a survey-style system prompt and a deck that DELIBERATELY excludes the
|
|
13
|
+
* `task` card. That exclusion is the recursion guard: a sub-agent with no `task`
|
|
14
|
+
* tool cannot spawn its own sub-agents. The runner submits one prompt, lets the
|
|
15
|
+
* sub-agent run its own tool loop to completion, then extracts the final
|
|
16
|
+
* assistant turn's text as the single report handed back to the parent.
|
|
17
|
+
*
|
|
18
|
+
* The runner never throws out of `run()`: a model that does not resolve, a
|
|
19
|
+
* sub-agent error, or an empty transcript all map to a `{ ok:false, report }`
|
|
20
|
+
* result so a delegation failure surfaces to the parent agent as an ordinary
|
|
21
|
+
* tool error rather than crashing the turn. Cancellation is forwarded: an
|
|
22
|
+
* already-aborted signal short-circuits, and an abort raised mid-run calls
|
|
23
|
+
* `agent.abort()`.
|
|
24
|
+
*
|
|
25
|
+
* The `spawn`/`tools` options are pure test seams — they let a unit test drive
|
|
26
|
+
* the runner with an in-memory fake instead of a real network round-trip.
|
|
27
|
+
*/
|
|
28
|
+
import { type AgentMessage } from "../../conductor";
|
|
29
|
+
import { type AgentTool } from "../../capability-deck";
|
|
30
|
+
import type { DelegateRunner } from "../../capability-deck/cards/task-card";
|
|
31
|
+
/**
|
|
32
|
+
* The minimal sub-agent surface the runner drives.
|
|
33
|
+
*
|
|
34
|
+
* A real framework {@link Agent} satisfies this structurally; a test passes a
|
|
35
|
+
* lightweight fake via {@link DelegateRunnerOptions.spawn}. Only the pieces the
|
|
36
|
+
* runner touches are named — submit a prompt, abort, and read the resulting
|
|
37
|
+
* transcript/error afterward.
|
|
38
|
+
*/
|
|
39
|
+
export interface DelegateSubAgent {
|
|
40
|
+
prompt(input: string): Promise<void>;
|
|
41
|
+
abort(): void;
|
|
42
|
+
readonly state: {
|
|
43
|
+
messages: readonly AgentMessage[];
|
|
44
|
+
error?: string;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Configuration for {@link createDelegateRunner}. */
|
|
48
|
+
export interface DelegateRunnerOptions {
|
|
49
|
+
/** The model id the sub-agent runs under (the parent's resolved model). */
|
|
50
|
+
readonly modelId: string;
|
|
51
|
+
/** The working directory the sub-agent's deck is scoped to. */
|
|
52
|
+
readonly cwd: string;
|
|
53
|
+
/** The system prompt that shapes the sub-agent's behaviour. */
|
|
54
|
+
readonly system: string;
|
|
55
|
+
/**
|
|
56
|
+
* Per-call credential resolver, forwarded to the framework `Agent` unchanged
|
|
57
|
+
* (OAuth-only providers, short-lived token rotation). Omitted from the agent
|
|
58
|
+
* options entirely when undefined so the framework env-var lookup still wins.
|
|
59
|
+
*/
|
|
60
|
+
readonly getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Test seam: build the sub-agent from the objective instead of constructing a
|
|
63
|
+
* real framework `Agent`. When omitted the runner uses `new Agent`.
|
|
64
|
+
*/
|
|
65
|
+
readonly spawn?: (objective: string, context?: string) => DelegateSubAgent;
|
|
66
|
+
/**
|
|
67
|
+
* Test seam: the tool deck the sub-agent runs with. When omitted the runner
|
|
68
|
+
* provisions the read-only `'authoring'` profile (which already excludes the
|
|
69
|
+
* `task` card — the recursion guard).
|
|
70
|
+
*/
|
|
71
|
+
readonly tools?: () => AgentTool[];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Build a live {@link DelegateRunner} the host wires into the deck context.
|
|
75
|
+
*
|
|
76
|
+
* The model is resolved once up front; if the id resolves to nothing the runner
|
|
77
|
+
* still builds but every `run()` reports `{ ok:false }` rather than throwing, so
|
|
78
|
+
* a misconfigured model can never crash the parent's turn.
|
|
79
|
+
*
|
|
80
|
+
* @param opts the model id, cwd, system prompt, and optional credential/test seams
|
|
81
|
+
* @returns a runner satisfying the task card's {@link DelegateRunner} contract
|
|
82
|
+
*/
|
|
83
|
+
export declare function createDelegateRunner(opts: DelegateRunnerOptions): DelegateRunner;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delegate-runner wiring (#10 / fixes #9).
|
|
3
|
+
*
|
|
4
|
+
* Proves the chain that makes the advertised `task` tool functional: a live
|
|
5
|
+
* {@link createDelegateRunner} spins an isolated sub-agent, runs one objective,
|
|
6
|
+
* and reports back the sub-agent's final assistant text; and the `task` card,
|
|
7
|
+
* given that runner under {@link DELEGATE_HANDLE_KEY}, delegates for real instead
|
|
8
|
+
* of returning its `STUB_NOTE`.
|
|
9
|
+
*
|
|
10
|
+
* Every test drives the runner through the `opts.spawn` / `opts.tools` seams so
|
|
11
|
+
* no model is resolved and no network is touched.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable cross-session memory — a disk-backed working-memory store and the
|
|
3
|
+
* project-context document that carries it into each session's system prompt.
|
|
4
|
+
*
|
|
5
|
+
* The `memory` capability ({@link "../../capability-deck/cards/memory-card"})
|
|
6
|
+
* already reads a host-injected store from `ctx.framework[MEMORY_HANDLE_KEY]` and
|
|
7
|
+
* falls back to an in-process buffer when none is wired. This module supplies the
|
|
8
|
+
* missing piece: a synchronous {@link DiskMemoryStore} that persists the agent's
|
|
9
|
+
* note to a per-cwd `MEMORY.md` under the profile directory, so a fact written in
|
|
10
|
+
* one session is still there the next time the agent runs in the same directory.
|
|
11
|
+
*
|
|
12
|
+
* The same file is loaded into the briefing as a {@link ContextDoc} ({@link
|
|
13
|
+
* loadMemoryDoc}) so the model *sees* its prior memory at the top of a fresh
|
|
14
|
+
* session, not only when it explicitly calls the `memory` tool. The body is
|
|
15
|
+
* line-and-byte capped by {@link truncateEntrypointContent} so a runaway memory
|
|
16
|
+
* file cannot bloat the prompt.
|
|
17
|
+
*
|
|
18
|
+
* Design notes:
|
|
19
|
+
* - The store is STRICTLY SYNCHRONOUS — the {@link MemoryStore} contract the
|
|
20
|
+
* card validates is sync (`read`/`replace`/`append` return `void`/`string`),
|
|
21
|
+
* and an async store would silently fail that validation and degrade to the
|
|
22
|
+
* in-memory fallback.
|
|
23
|
+
* - The memory directory is scoped UNDER `workspace.profileDir`, NOT the cwd, so
|
|
24
|
+
* persisting memory never writes files into the user's working tree.
|
|
25
|
+
* - The cwd is slugged with the SAME regex as {@link "./session".sessionScopeDir}
|
|
26
|
+
* so the per-cwd partitioning lines up with the session layout.
|
|
27
|
+
* - Files are written `0o600` (owner-only), matching the auth vault — the memory
|
|
28
|
+
* note can hold sensitive project facts.
|
|
29
|
+
*/
|
|
30
|
+
import type { ContextDoc } from "../../briefing";
|
|
31
|
+
import { MEMORY_HANDLE_KEY, type MemoryStore } from "../../capability-deck";
|
|
32
|
+
import type { Workspace } from "../contract";
|
|
33
|
+
/** The on-disk filename the durable memory note is stored under. */
|
|
34
|
+
export declare const MEMORY_ENTRYPOINT = "MEMORY.md";
|
|
35
|
+
/** Maximum number of lines of the memory note inlined into the prompt. */
|
|
36
|
+
export declare const MAX_ENTRYPOINT_LINES = 200;
|
|
37
|
+
/**
|
|
38
|
+
* Maximum bytes of the memory note inlined into the prompt.
|
|
39
|
+
*
|
|
40
|
+
* ~125 chars/line at 200 lines: this catches long-line indexes that slip past
|
|
41
|
+
* the line cap (a few very long lines can be far larger than 200 short ones).
|
|
42
|
+
*/
|
|
43
|
+
export declare const MAX_ENTRYPOINT_BYTES = 25000;
|
|
44
|
+
/**
|
|
45
|
+
* Cap the memory note to {@link MAX_ENTRYPOINT_LINES} and
|
|
46
|
+
* {@link MAX_ENTRYPOINT_BYTES}, appending a warning naming which cap fired.
|
|
47
|
+
*
|
|
48
|
+
* Line-truncates first (a natural boundary), then byte-truncates at the last
|
|
49
|
+
* newline before the cap so a line is never cut mid-way. Content within both
|
|
50
|
+
* caps is returned trimmed and unchanged.
|
|
51
|
+
*
|
|
52
|
+
* @param raw the raw memory-file text
|
|
53
|
+
* @returns the prompt-safe body (possibly with a trailing truncation warning)
|
|
54
|
+
*/
|
|
55
|
+
export declare function truncateEntrypointContent(raw: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* The per-cwd memory directory under the profile dir: `<profileDir>/memory/--<slug>--`.
|
|
58
|
+
*
|
|
59
|
+
* The cwd is slugged — every non-alphanumeric run collapsed to a single dash —
|
|
60
|
+
* and wrapped in `--…--` markers, the SAME scheme `sessionScopeDir` uses for the
|
|
61
|
+
* session transcript layout, so the two partitionings line up. The directory
|
|
62
|
+
* lives under `workspace.profileDir`, never inside the repo, so persisted memory
|
|
63
|
+
* never pollutes the user's working tree.
|
|
64
|
+
*
|
|
65
|
+
* @param workspace the resolved on-disk layout (supplies the profile dir)
|
|
66
|
+
* @param cwd the run's working directory
|
|
67
|
+
*/
|
|
68
|
+
export declare function memoryDirFor(workspace: Workspace, cwd: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* A synchronous, disk-backed {@link MemoryStore} persisting the working-memory
|
|
71
|
+
* note to `<memDir>/MEMORY.md`.
|
|
72
|
+
*
|
|
73
|
+
* Satisfies the `memory` card's narrow three-method port. STRICTLY synchronous:
|
|
74
|
+
* the card validates that each of `read`/`replace`/`append` is a function and
|
|
75
|
+
* calls them inline, so any async variant would break the contract and silently
|
|
76
|
+
* fall back to the in-memory store. Writes create the directory and use mode
|
|
77
|
+
* `0o600` (owner-only), matching the auth vault.
|
|
78
|
+
*/
|
|
79
|
+
export declare class DiskMemoryStore implements MemoryStore {
|
|
80
|
+
private readonly memDir;
|
|
81
|
+
private readonly file;
|
|
82
|
+
constructor(memDir: string);
|
|
83
|
+
/** The current note, or `""` when no file has been written yet. */
|
|
84
|
+
read(): string;
|
|
85
|
+
/** Overwrite the note, creating the memory directory as needed (mode 0o600). */
|
|
86
|
+
replace(content: string): void;
|
|
87
|
+
/** Add a single line to the end of the note (composed of read + replace). */
|
|
88
|
+
append(line: string): void;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Load the per-cwd memory note as a {@link ContextDoc} for the briefing's
|
|
92
|
+
* `# Project context` section, or `undefined` when there is nothing to show.
|
|
93
|
+
*
|
|
94
|
+
* An absent file, an empty/whitespace-only note, or any read error yields
|
|
95
|
+
* `undefined` (no project-context block); a non-empty note is capped by
|
|
96
|
+
* {@link truncateEntrypointContent} before inlining. The doc's `path` is the
|
|
97
|
+
* bare `MEMORY.md` label, not the on-disk location, so the heading reads cleanly.
|
|
98
|
+
*
|
|
99
|
+
* @param memDir the per-cwd memory directory from {@link memoryDirFor}
|
|
100
|
+
*/
|
|
101
|
+
export declare function loadMemoryDoc(memDir: string): ContextDoc | undefined;
|
|
102
|
+
/** Re-exported so hosts wiring a store can use one import. */
|
|
103
|
+
export { MEMORY_HANDLE_KEY };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable cross-session memory (memdir, #27).
|
|
3
|
+
*
|
|
4
|
+
* Proves the disk-backed memory chain: {@link memoryDirFor} scopes a per-cwd
|
|
5
|
+
* directory under the profile (never the cwd); {@link DiskMemoryStore} round-trips
|
|
6
|
+
* the `memory` card's sync `read`/`replace`/`append` to `<memDir>/MEMORY.md` with
|
|
7
|
+
* owner-only perms; and {@link loadMemoryDoc} surfaces that file as a briefing
|
|
8
|
+
* {@link ContextDoc} (capped by {@link truncateEntrypointContent}) — absent or
|
|
9
|
+
* empty for a fresh cwd. Together they let a fact written in one session reappear
|
|
10
|
+
* in the next session's prompt.
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-session read-state store — the product half of the read-before-edit gate
|
|
3
|
+
* (read-edit-gate, #11).
|
|
4
|
+
*
|
|
5
|
+
* The framework's file tools (read/write/edit) consult a host-injected handle on
|
|
6
|
+
* `ctx.framework` under the string-literal key {@link READ_STATE_HANDLE_KEY} to
|
|
7
|
+
* decide whether an edit/write may proceed: a file must have been read before it
|
|
8
|
+
* is mutated, and an edit is refused when the file changed on disk since that
|
|
9
|
+
* read. This module supplies the missing host piece — an in-memory map of which
|
|
10
|
+
* files have been read this session and the stat fingerprint at read time — so
|
|
11
|
+
* the gate has somewhere to record and look up that state.
|
|
12
|
+
*
|
|
13
|
+
* Design notes:
|
|
14
|
+
* - The store is a thin wrapper over a `Map`, keyed by `path.normalize(absPath)`
|
|
15
|
+
* so a write and a later lookup of the SAME file agree on one key regardless
|
|
16
|
+
* of how the path was spelled (trailing slashes, `.`/`..` segments).
|
|
17
|
+
* - One instance is created PER SESSION (in `session.ts` `selectTools`) and
|
|
18
|
+
* shared across the read/write/edit cards through the single `ctx.framework`
|
|
19
|
+
* bag, so a read recorded by one tool is visible to the gate in another.
|
|
20
|
+
* - The record carries the stat fields the framework gate compares against:
|
|
21
|
+
* `mtimeMs` (the file's last-modified time at read), `size`, an optional
|
|
22
|
+
* `contentHash` for a content-equality fallback, and `readAt` (when the host
|
|
23
|
+
* recorded the read). The framework's own stat result exposes the modified
|
|
24
|
+
* time as `modifiedMs`; the host normalizes it into `mtimeMs` on the way in,
|
|
25
|
+
* so this record is the single agreed shape.
|
|
26
|
+
* - The handle key is a bare string literal (mirroring the deck's
|
|
27
|
+
* `DELEGATE_HANDLE_KEY` / `MEMORY_HANDLE_KEY`) so the framework consumer and
|
|
28
|
+
* this product injector agree WITHOUT a cross-package type import. `ctx.framework`
|
|
29
|
+
* is an open record, so the injection typechecks immediately.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* The string-literal key under which the host wires this store onto the deck's
|
|
33
|
+
* `ctx.framework` bag.
|
|
34
|
+
*
|
|
35
|
+
* Mirrors the existing `DELEGATE_HANDLE_KEY = "delegate"` and
|
|
36
|
+
* `MEMORY_HANDLE_KEY = "memoryStore"` conventions. The framework's file tools
|
|
37
|
+
* read `ctx.framework["readState"]` by the same literal, so the two sides agree
|
|
38
|
+
* by string rather than by a shared imported symbol.
|
|
39
|
+
*/
|
|
40
|
+
export declare const READ_STATE_HANDLE_KEY: "readState";
|
|
41
|
+
/**
|
|
42
|
+
* One file's read fingerprint, recorded when the agent reads it this session.
|
|
43
|
+
*
|
|
44
|
+
* - `mtimeMs` — the file's last-modified time (ms) at read. The gate refuses an
|
|
45
|
+
* edit when the on-disk mtime has advanced past this. (The framework's stat
|
|
46
|
+
* result names this `modifiedMs`; the host maps it to `mtimeMs` here.)
|
|
47
|
+
* - `size` — the file's byte length at read, a cheap secondary change signal.
|
|
48
|
+
* - `contentHash` — optional digest of the content read, for a content-equality
|
|
49
|
+
* fallback when the mtime advanced but the bytes are unchanged.
|
|
50
|
+
* - `readAt` — when the host recorded this read (ms since epoch).
|
|
51
|
+
*/
|
|
52
|
+
export interface ReadStateRecord {
|
|
53
|
+
readonly mtimeMs: number;
|
|
54
|
+
readonly size: number;
|
|
55
|
+
readonly contentHash?: string;
|
|
56
|
+
readonly readAt: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* In-memory map of read files for one session, keyed by normalized absolute path.
|
|
60
|
+
*
|
|
61
|
+
* The framework gate `set`s a record after every read and after every successful
|
|
62
|
+
* mutation, and `get`s/`has`-checks it before an edit. Keys are normalized in one
|
|
63
|
+
* place ({@link ReadStateStore.key}) so writes and lookups of the same file land
|
|
64
|
+
* on the same entry no matter how the caller spelled the path.
|
|
65
|
+
*/
|
|
66
|
+
export declare class ReadStateStore {
|
|
67
|
+
private readonly entries;
|
|
68
|
+
/** Normalize an absolute path into the store's canonical key. */
|
|
69
|
+
private key;
|
|
70
|
+
/** The recorded read fingerprint for a path, or `undefined` if never read. */
|
|
71
|
+
get(absPath: string): ReadStateRecord | undefined;
|
|
72
|
+
/** Record (or replace) the read fingerprint for a path; returns this store. */
|
|
73
|
+
set(absPath: string, value: ReadStateRecord): this;
|
|
74
|
+
/** Whether a path has a recorded read fingerprint this session. */
|
|
75
|
+
has(absPath: string): boolean;
|
|
76
|
+
/** Drop a path's recorded read fingerprint; returns whether one existed. */
|
|
77
|
+
delete(absPath: string): boolean;
|
|
78
|
+
/** Forget every recorded read for this session. */
|
|
79
|
+
clear(): void;
|
|
80
|
+
}
|
|
81
|
+
/** Mint a fresh, empty per-session read-state store. */
|
|
82
|
+
export declare function createReadStateStore(): ReadStateStore;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-session read-state store (read-edit-gate, #11, product half).
|
|
3
|
+
*
|
|
4
|
+
* Proves the in-memory map the read-before-edit gate records into: records are
|
|
5
|
+
* keyed by `path.normalize(absPath)` (so differently-spelled paths to the same
|
|
6
|
+
* file collapse to one entry), `get`/`set`/`has`/`delete`/`clear` round-trip the
|
|
7
|
+
* `{ mtimeMs, size, contentHash?, readAt }` record, the handle key matches the
|
|
8
|
+
* literal the framework consumer reads, and each session gets its own store.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
@@ -8,8 +8,12 @@
|
|
|
8
8
|
* {@link ModelMatcher}; the conductor itself is built lazily so no framework agent
|
|
9
9
|
* is constructed until the first turn runs.
|
|
10
10
|
*/
|
|
11
|
-
import { type SessionConductor } from "../../conductor";
|
|
12
|
-
import type
|
|
11
|
+
import { type AgentTool, type SessionConductor } from "../../conductor";
|
|
12
|
+
import { type MemoryStore } from "../../capability-deck";
|
|
13
|
+
import { type DelegateRunner } from "../../capability-deck/cards/task-card";
|
|
14
|
+
import { type CheckpointStore } from "./checkpoint";
|
|
15
|
+
import { type ContextDoc, type SkillCard } from "../../briefing";
|
|
16
|
+
import type { BootContext, Invocation } from "../contract";
|
|
13
17
|
/**
|
|
14
18
|
* Resolve the model id for this run.
|
|
15
19
|
*
|
|
@@ -21,6 +25,37 @@ import type { BootContext } from "../contract";
|
|
|
21
25
|
* @returns the canonical model id to bind the session to
|
|
22
26
|
*/
|
|
23
27
|
export declare function resolveModelId(ctx: BootContext): string;
|
|
28
|
+
/**
|
|
29
|
+
* Gather the on-disk skill cards the model may invoke this run: walk the project
|
|
30
|
+
* and user `.indusagi/skills` roots and drop any card flagged
|
|
31
|
+
* `disable-model-invocation` (those stay loadable explicitly, but the model is
|
|
32
|
+
* not told about them). A filesystem walk error degrades to an empty list so a
|
|
33
|
+
* bad/unreadable skills dir never sinks the session.
|
|
34
|
+
*/
|
|
35
|
+
export declare function gatherModelSkills(cwd: string): SkillCard[];
|
|
36
|
+
/**
|
|
37
|
+
* Select the tool deck for the run, honouring `--no-tools` (empty) and `--tools`
|
|
38
|
+
* (allow-list). Tool ids are matched case-insensitively with `_`/`-` stripped, so
|
|
39
|
+
* a `--tools web_fetch,todo_read` request lines up with the deck's `webfetch` /
|
|
40
|
+
* `todoread` ids.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* @param runner an optional live sub-agent {@link DelegateRunner}; when present it
|
|
44
|
+
* is injected under {@link DELEGATE_HANDLE_KEY} so the `task` card delegates for
|
|
45
|
+
* real instead of returning its `STUB_NOTE`. Omitted by callers that only need
|
|
46
|
+
* a deck (the card then degrades gracefully).
|
|
47
|
+
* @param checkpoint an optional per-session {@link CheckpointStore}; when present
|
|
48
|
+
* it is injected under {@link CHECKPOINT_HANDLE_KEY} so the framework's
|
|
49
|
+
* write/edit tools snapshot a file's pre-mutation content (rewind, #24). Omitted
|
|
50
|
+
* callers get no checkpointing (the tools no-op the snapshot).
|
|
51
|
+
*/
|
|
52
|
+
export declare function selectTools(cwd: string, inv: Invocation, runner?: DelegateRunner, memoryStore?: MemoryStore, checkpoint?: CheckpointStore): AgentTool[];
|
|
53
|
+
/**
|
|
54
|
+
* Compose the run's system prompt: `--system` replaces the built-in briefing,
|
|
55
|
+
* `--append-system` adds a trailing block, and both compose (override then
|
|
56
|
+
* append). With neither, it is the tool-aware built-in briefing.
|
|
57
|
+
*/
|
|
58
|
+
export declare function composeSystem(tools: AgentTool[], inv: Invocation, cwd: string, skills?: readonly SkillCard[], memoryDoc?: ContextDoc): string;
|
|
24
59
|
/**
|
|
25
60
|
* The cwd-scoped session directory under the workspace `sessions/` root.
|
|
26
61
|
*
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills-surface wiring (#7 / fixes #11).
|
|
3
|
+
*
|
|
4
|
+
* Proves the chain that makes on-disk `SKILL.md` cards visible to the model:
|
|
5
|
+
* `gatherModelSkills` walks `cwd/.indusagi/skills`, drops any card flagged
|
|
6
|
+
* `disable-model-invocation`, and the survivors render into the briefing's
|
|
7
|
+
* `<available_skills>` block via `composeSystem`. A `--system` override must NOT
|
|
8
|
+
* carry the skills block (it replaces the whole prompt).
|
|
9
|
+
*/
|
|
10
|
+
export {};
|