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
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project-context document loader — gathers repository convention files
|
|
3
|
+
* (CLAUDE.md / AGENTS.md and brand variants) into {@link ContextDoc}s that the
|
|
4
|
+
* briefing's `PROJECT_CONTEXT_SECTION` inlines under `# Project context`.
|
|
5
|
+
*
|
|
6
|
+
* The walk runs the ancestor chain from the filesystem root down to the cwd, so
|
|
7
|
+
* the cwd's own files land LAST in the returned array — and since the renderer
|
|
8
|
+
* treats array order as priority order, the closest (most specific) file wins.
|
|
9
|
+
* The home directory is scanned after the chain as a global fallback. Each file
|
|
10
|
+
* may pull additional files inline via `@import` references (bounded to a depth
|
|
11
|
+
* of {@link DEFAULT_MAX_IMPORT_DEPTH} with a per-walk realpath guard so cycles
|
|
12
|
+
* and symlink loops cannot recurse or hang).
|
|
13
|
+
*
|
|
14
|
+
* The whole body is wrapped so that a permissions error on one ancestor, a
|
|
15
|
+
* binary file, or a malformed tree degrades to `[]` rather than crashing boot —
|
|
16
|
+
* repo conventions are a best-effort enrichment, never a hard dependency.
|
|
17
|
+
*/
|
|
18
|
+
import type { ContextDoc } from "./contract";
|
|
19
|
+
/** Options for {@link gatherContextDocs}. Every field is optional. */
|
|
20
|
+
export interface GatherContextDocsOptions {
|
|
21
|
+
/** Maximum `@import` recursion depth. Defaults to {@link DEFAULT_MAX_IMPORT_DEPTH}. */
|
|
22
|
+
readonly maxImportDepth?: number;
|
|
23
|
+
/** Maximum bytes retained per document body (post-trim). Defaults to {@link DEFAULT_MAX_BYTES_PER_DOC}. */
|
|
24
|
+
readonly maxBytesPerDoc?: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Gather the repository's project-context documents into an ordered
|
|
28
|
+
* {@link ContextDoc} array for the briefing.
|
|
29
|
+
*
|
|
30
|
+
* Order: the ancestor chain from root → cwd (so cwd's files land last = highest
|
|
31
|
+
* priority), then the home directory as a global fallback. Within each directory
|
|
32
|
+
* the {@link CANDIDATE_FILES} are read in their listed order. Each file may pull
|
|
33
|
+
* `@import` children inline (depth-bounded, cycle-guarded). De-duplication is by
|
|
34
|
+
* realpath, so a file reachable through two roots is inlined once.
|
|
35
|
+
*
|
|
36
|
+
* Never throws: any failure in the walk yields the docs gathered so far (or `[]`).
|
|
37
|
+
*/
|
|
38
|
+
export declare function gatherContextDocs(cwd: string, home: string, opts?: GatherContextDocsOptions): readonly ContextDoc[];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project-context loader — colocated unit tests.
|
|
3
|
+
*
|
|
4
|
+
* Every test runs against a fresh tmpdir tree (the `src/boot/boot.test.ts`
|
|
5
|
+
* sandbox pattern): no real home directory, no network. The realpath of the
|
|
6
|
+
* tmpdir base is taken up front because macOS resolves `/var/folders` → a
|
|
7
|
+
* `/private/...` symlink, which the loader's realpath de-dup would otherwise
|
|
8
|
+
* surface in path assertions.
|
|
9
|
+
*
|
|
10
|
+
* Pinned behaviors:
|
|
11
|
+
* - a cwd CLAUDE.md body is inlined;
|
|
12
|
+
* - an ancestor AGENTS.md orders BEFORE the cwd doc (cwd = highest priority);
|
|
13
|
+
* - an `@import` pulls a sibling file inline;
|
|
14
|
+
* - a cyclic import terminates (does not hang) and inlines each file once;
|
|
15
|
+
* - missing files and binary (non-text-extension) imports are skipped;
|
|
16
|
+
* - a home-root CLAUDE.md is included exactly once even when cwd is under home.
|
|
17
|
+
*/
|
|
18
|
+
export {};
|
|
@@ -25,3 +25,5 @@ export { scanMacroBody, resolveTokens, applyMacros, buildMacroScope, expandInvoc
|
|
|
25
25
|
export type { LoadMacrosOptions, FrontmatterSplit } from "./macros";
|
|
26
26
|
export { loadSkillCards, gatherSkillCards, modelInvocableCards, } from "./skills";
|
|
27
27
|
export type { SkillRoot } from "./skills";
|
|
28
|
+
export { gatherContextDocs } from "./context-docs";
|
|
29
|
+
export type { GatherContextDocsOptions } from "./context-docs";
|
|
@@ -23,8 +23,14 @@ export { daemonCard, buildDaemonCapability, DaemonTable, type DaemonState, type
|
|
|
23
23
|
export { taskCard, buildTaskCapability, DELEGATE_HANDLE_KEY, type DelegateRunner, type DelegateRequest, type DelegateResult, type TaskParamsType, type TaskDetails, } from "./task-card";
|
|
24
24
|
export { saasCard, buildSaasCapability, SAAS_GATEWAY_KEY, type SaasGatewayPort, type RemoteToolSummary, type RemoteExecution, type SaasParamsType, type SaasDetails, } from "./saas-card";
|
|
25
25
|
export { memoryCard, buildMemoryCapability, InMemoryStore, MEMORY_HANDLE_KEY, type MemoryStore, type MemoryParamsType, type MemoryDetails, } from "./memory-card";
|
|
26
|
+
export { enterPlanModeCard, exitPlanModeCard, buildEnterPlanModeCapability, buildExitPlanModeCapability, PLAN_HANDLE_KEY, type PlanController, type EnterPlanParamsType, type EnterPlanDetails, type ExitPlanParamsType, type ExitPlanDetails, } from "./plan-tools";
|
|
27
|
+
export { planSlug, planFilePath, writePlan, readPlan, PLANS_DIRNAME } from "./plan-file";
|
|
26
28
|
/**
|
|
27
29
|
* The app-novel cards, in catalog order. The manifest module concatenates these
|
|
28
30
|
* with the builtin-bridge cards to build the full `CAPABILITY_CARDS` array.
|
|
31
|
+
*
|
|
32
|
+
* The plan-mode tools live here (the broadest `all` profile only): plan mode is a
|
|
33
|
+
* gate over mutating work, so the tools are only meaningful in a full-access deck —
|
|
34
|
+
* never the read-only `authoring` subset.
|
|
29
35
|
*/
|
|
30
36
|
export declare const APP_NOVEL_CARDS: readonly CapabilityCard[];
|
|
@@ -5,16 +5,15 @@
|
|
|
5
5
|
* Status: minimal in-memory implementation + clearly-typed seam for the
|
|
6
6
|
* framework memory subsystem.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* the tool's wire contract do not change
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* working-memory store; read it from `ctx.framework[MEMORY_HANDLE_KEY]`.
|
|
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.
|
|
18
17
|
*
|
|
19
18
|
* The single tool keys behavior on an `action` discriminant:
|
|
20
19
|
* - `read` — return the current working-memory note.
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
/** The directory plans are written under (relative to the session scope dir). */
|
|
19
|
+
export declare const PLANS_DIRNAME: "plans";
|
|
20
|
+
/**
|
|
21
|
+
* Derive a filesystem-safe slug from a plan's text — its first non-empty line,
|
|
22
|
+
* lower-cased, with every run of non-alphanumeric characters collapsed to a
|
|
23
|
+
* single dash and the result trimmed and length-capped. Falls back to `"plan"`
|
|
24
|
+
* when the text yields nothing usable (empty, or punctuation-only).
|
|
25
|
+
*
|
|
26
|
+
* @param plan the plan body whose first line seeds the slug
|
|
27
|
+
*/
|
|
28
|
+
export declare function planSlug(plan: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the absolute path a plan with `slug` is written to under `sessionDir`.
|
|
31
|
+
* The `plans/` sub-directory is implied; callers pass the session scope dir and
|
|
32
|
+
* get back `<sessionDir>/plans/<slug>.md`.
|
|
33
|
+
*
|
|
34
|
+
* @param sessionDir the per-session directory plans live beneath
|
|
35
|
+
* @param slug the derived slug (see {@link planSlug})
|
|
36
|
+
*/
|
|
37
|
+
export declare function planFilePath(sessionDir: string, slug: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Write the plan body to a slug-named markdown file under `sessionDir/plans`,
|
|
40
|
+
* creating the directory as needed, and return the absolute path written. The
|
|
41
|
+
* filename is `<short-timestamp>-<slug>.md` so repeated plans in one session do
|
|
42
|
+
* not overwrite each other.
|
|
43
|
+
*
|
|
44
|
+
* Throws on an I/O failure (the conductor swallows it into a non-fatal note).
|
|
45
|
+
*
|
|
46
|
+
* @param sessionDir the per-session directory plans live beneath
|
|
47
|
+
* @param plan the plan body to persist
|
|
48
|
+
*/
|
|
49
|
+
export declare function writePlan(sessionDir: string, plan: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Read a previously-written plan file back, or `undefined` when it is missing or
|
|
52
|
+
* unreadable. Used by tests and by any review surface that wants the saved plan.
|
|
53
|
+
*
|
|
54
|
+
* @param path the absolute plan-file path (see {@link planFilePath} / {@link writePlan})
|
|
55
|
+
*/
|
|
56
|
+
export declare function readPlan(path: string): string | undefined;
|
|
@@ -0,0 +1,97 @@
|
|
|
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 Static } from "@sinclair/typebox";
|
|
26
|
+
import type { Capability, CapabilityCard, DeckContext } from "../contract";
|
|
27
|
+
/**
|
|
28
|
+
* Reserved key under which a host MAY wire a plan-mode controller into the deck
|
|
29
|
+
* context. The conductor-driven design intercepts the plan tools' result details
|
|
30
|
+
* directly (no handle required), so this is exported only for forward-compat /
|
|
31
|
+
* an alternate host that wants the tool to drive the flip itself.
|
|
32
|
+
*/
|
|
33
|
+
export declare const PLAN_HANDLE_KEY: "planController";
|
|
34
|
+
/**
|
|
35
|
+
* The narrow port a host may wire to flip the live session into plan mode. Unused
|
|
36
|
+
* by the default (conductor-intercept) wiring; kept for hosts that prefer the tool
|
|
37
|
+
* to signal directly rather than via the tool-result seam.
|
|
38
|
+
*/
|
|
39
|
+
export interface PlanController {
|
|
40
|
+
/** Switch the session into read-only plan mode for subsequent tool calls. */
|
|
41
|
+
enterPlanMode(): void;
|
|
42
|
+
}
|
|
43
|
+
declare const EnterPlanParams: import("@sinclair/typebox").TObject<{}>;
|
|
44
|
+
/** Statically-inferred parameter type EnterPlanMode's `execute` receives. */
|
|
45
|
+
export type EnterPlanParamsType = Static<typeof EnterPlanParams>;
|
|
46
|
+
/** Structured detail returned by EnterPlanMode. */
|
|
47
|
+
export interface EnterPlanDetails {
|
|
48
|
+
/**
|
|
49
|
+
* The marker the conductor intercepts on the tool-result seam to switch the
|
|
50
|
+
* session into plan mode (and capture the pre-plan mode for a later restore).
|
|
51
|
+
*/
|
|
52
|
+
readonly enterPlanMode: true;
|
|
53
|
+
/** Whether an optional in-context controller also applied the switch directly. */
|
|
54
|
+
readonly applied: boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Build the EnterPlanMode capability. On invoke it asks the wired
|
|
58
|
+
* {@link PlanController} to switch the session into plan mode, then returns
|
|
59
|
+
* explore-only prose. It is `readOnly: true`, so the permission gate always
|
|
60
|
+
* permits it (including inside plan mode itself).
|
|
61
|
+
*
|
|
62
|
+
* @param ctx the deck context; an optional controller is read from
|
|
63
|
+
* `ctx.framework[PLAN_HANDLE_KEY]`.
|
|
64
|
+
*/
|
|
65
|
+
export declare function buildEnterPlanModeCapability(ctx: DeckContext): Capability<typeof EnterPlanParams, EnterPlanDetails>;
|
|
66
|
+
declare const ExitPlanParams: import("@sinclair/typebox").TObject<{
|
|
67
|
+
plan: import("@sinclair/typebox").TString;
|
|
68
|
+
}>;
|
|
69
|
+
/** Statically-inferred parameter type ExitPlanMode's `execute` receives. */
|
|
70
|
+
export type ExitPlanParamsType = Static<typeof ExitPlanParams>;
|
|
71
|
+
/**
|
|
72
|
+
* Structured detail returned by ExitPlanMode. The conductor watches the
|
|
73
|
+
* tool-result seam for `exitPlan === true`, captures the {@link plan}, and runs
|
|
74
|
+
* the approval handshake (the tool itself does not flip the mode or prompt).
|
|
75
|
+
*/
|
|
76
|
+
export interface ExitPlanDetails {
|
|
77
|
+
/** The marker the conductor intercepts to start the exit-plan handshake. */
|
|
78
|
+
readonly exitPlan: true;
|
|
79
|
+
/** The plan text the model authored, surfaced to the user for approval. */
|
|
80
|
+
readonly plan: string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Build the ExitPlanMode capability. It performs no mode flip and no prompt of
|
|
84
|
+
* its own — it returns the `{ exitPlan: true, plan }` detail the conductor
|
|
85
|
+
* intercepts. The model-facing content echoes the proposed plan so a transcript
|
|
86
|
+
* reader sees what was put up for approval.
|
|
87
|
+
*
|
|
88
|
+
* Not marked read-only: it represents the request to RESUME mutating work, so in
|
|
89
|
+
* plan mode it must still be permitted explicitly (the gate special-cases the
|
|
90
|
+
* plan tools alongside read-only tools — see the conductor wiring).
|
|
91
|
+
*/
|
|
92
|
+
export declare function buildExitPlanModeCapability(_ctx: DeckContext): Capability<typeof ExitPlanParams, ExitPlanDetails>;
|
|
93
|
+
/** Catalog row for the EnterPlanMode capability. */
|
|
94
|
+
export declare const enterPlanModeCard: CapabilityCard;
|
|
95
|
+
/** Catalog row for the ExitPlanMode capability. */
|
|
96
|
+
export declare const exitPlanModeCard: CapabilityCard;
|
|
97
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan-mode cards + plan-file helpers — focused unit tests.
|
|
3
|
+
*
|
|
4
|
+
* The cards are pure capability builders: EnterPlanMode is read-only and returns
|
|
5
|
+
* the `{ enterPlanMode: true }` marker the conductor intercepts; ExitPlanMode
|
|
6
|
+
* returns the `{ exitPlan: true, plan }` detail the conductor's handshake reads.
|
|
7
|
+
* The plan-file helpers slug a title and round-trip the bytes on disk.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File checkpoint / code rewind — PRODUCT integration test (rewind, #24).
|
|
3
|
+
*
|
|
4
|
+
* This proves the checkpoint records through the ACTUAL wired path the product
|
|
5
|
+
* uses, NOT a hand-rolled harness:
|
|
6
|
+
*
|
|
7
|
+
* 1. We provision a real deck via {@link provisionDeck} with a `framework` bag
|
|
8
|
+
* carrying a real per-session {@link CheckpointStore} under the SAME
|
|
9
|
+
* `checkpoint` handle key `session.ts` injects (`CHECKPOINT_HANDLE_KEY`).
|
|
10
|
+
* 2. We pull the WIRED `edit` capability out of that deck — the same `AgentTool`
|
|
11
|
+
* object the conductor would run — and edit a file through it.
|
|
12
|
+
*
|
|
13
|
+
* The framework's edit tool calls `ctx.framework['checkpoint'].record(absPath,
|
|
14
|
+
* previous)` immediately before the write lands, with the file's OLD on-disk
|
|
15
|
+
* content. If the bridge failed to thread the store into `createEditTool`, the
|
|
16
|
+
* handle would be `undefined` and nothing would be recorded — so the assertions
|
|
17
|
+
* below only pass when the store reaches the tool through the real wiring.
|
|
18
|
+
*
|
|
19
|
+
* We then call `store.restore(nodeId)` and assert the on-disk content reverts to
|
|
20
|
+
* the pre-edit bytes, closing the loop end-to-end.
|
|
21
|
+
*
|
|
22
|
+
* Note: no `readState` handle is wired here, so the read-before-edit gate is OFF
|
|
23
|
+
* and the edit proceeds without a prior read — isolating the checkpoint behavior.
|
|
24
|
+
*/
|
|
25
|
+
export {};
|
|
@@ -31,7 +31,7 @@ export { CAPABILITY_CARDS, CAPABILITY_INDEX, CARD_PROFILES, capabilityIds, hasCa
|
|
|
31
31
|
* (checklist, background-process proxy, delegate/sub-agent, SaaS connector,
|
|
32
32
|
* working memory) plus their builders, stores, and injection-handle types.
|
|
33
33
|
*/
|
|
34
|
-
export { APP_NOVEL_CARDS, todoCard, buildTodoCapability, TodoLedger, type TodoItem, type TodoState, type TodoWeight, type TodoParamsType, type TodoDetails, daemonCard, buildDaemonCapability, DaemonTable, type DaemonState, type DaemonParamsType, type DaemonDetails, taskCard, buildTaskCapability, DELEGATE_HANDLE_KEY, type DelegateRunner, type DelegateRequest, type DelegateResult, type TaskParamsType, type TaskDetails, saasCard, buildSaasCapability, SAAS_GATEWAY_KEY, type SaasGatewayPort, type RemoteToolSummary, type RemoteExecution, type SaasParamsType, type SaasDetails, memoryCard, buildMemoryCapability, InMemoryStore, MEMORY_HANDLE_KEY, type MemoryStore, type MemoryParamsType, type MemoryDetails, } from "./cards/index";
|
|
34
|
+
export { APP_NOVEL_CARDS, todoCard, buildTodoCapability, TodoLedger, type TodoItem, type TodoState, type TodoWeight, type TodoParamsType, type TodoDetails, daemonCard, buildDaemonCapability, DaemonTable, type DaemonState, type DaemonParamsType, type DaemonDetails, taskCard, buildTaskCapability, DELEGATE_HANDLE_KEY, type DelegateRunner, type DelegateRequest, type DelegateResult, type TaskParamsType, type TaskDetails, saasCard, buildSaasCapability, SAAS_GATEWAY_KEY, type SaasGatewayPort, type RemoteToolSummary, type RemoteExecution, type SaasParamsType, type SaasDetails, memoryCard, buildMemoryCapability, InMemoryStore, MEMORY_HANDLE_KEY, type MemoryStore, type MemoryParamsType, type MemoryDetails, enterPlanModeCard, exitPlanModeCard, buildEnterPlanModeCapability, buildExitPlanModeCapability, PLAN_HANDLE_KEY, planSlug, planFilePath, writePlan, readPlan, PLANS_DIRNAME, type PlanController, type EnterPlanParamsType, type EnterPlanDetails, type ExitPlanParamsType, type ExitPlanDetails, } from "./cards/index";
|
|
35
35
|
/**
|
|
36
36
|
* Bridge ledger — event-sourced enrollment of dynamically grafted MCP tools:
|
|
37
37
|
* content-hash / ULID key minting, the immutable {@link BridgeLedger} value with
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-before-edit gate — PRODUCT integration test (read-edit-gate, #11).
|
|
3
|
+
*
|
|
4
|
+
* This proves the gate fires through the ACTUAL wired path the product uses:
|
|
5
|
+
*
|
|
6
|
+
* 1. We provision a real deck via {@link provisionDeck} with a `framework` bag
|
|
7
|
+
* that carries a real per-session {@link ReadStateStore} under the same
|
|
8
|
+
* `readState` handle key `session.ts` injects (`READ_STATE_HANDLE_KEY`).
|
|
9
|
+
* 2. We pull the WIRED `read` and `edit` capabilities out of that deck — the
|
|
10
|
+
* same `AgentTool` objects the conductor would run — and exercise them.
|
|
11
|
+
*
|
|
12
|
+
* The framework's edit tool consults `ctx.framework['readState']` to enforce the
|
|
13
|
+
* gate. If the bridge failed to thread the store into `createEditTool`, the
|
|
14
|
+
* handle would be `undefined` and the gate would no-op — so an edit of an unread
|
|
15
|
+
* file would succeed instead of being refused. These assertions therefore only
|
|
16
|
+
* pass when the store reaches the tool through the real wiring.
|
|
17
|
+
*
|
|
18
|
+
* The refusal message is byte-stable (the framework's `READ_BEFORE_EDIT_MESSAGE`)
|
|
19
|
+
* and asserted verbatim, so a wording drift on either side is caught.
|
|
20
|
+
*/
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bash command parser/classifier + catastrophic-command blocklist.
|
|
3
|
+
*
|
|
4
|
+
* The permission engine ({@link "./permissions"}) matches a tool call against
|
|
5
|
+
* induscode-style `Bash(...)` allow/ask/deny rules. For most tools the call is a
|
|
6
|
+
* single atomic action, so a rule's argument specifier matches against one
|
|
7
|
+
* subject string. The shell tool is different: `a && b; c | d` is FOUR commands
|
|
8
|
+
* in one call, and a coarse "match the whole command string" check is trivially
|
|
9
|
+
* bypassed — a benign-looking prefix can smuggle a destructive tail past an
|
|
10
|
+
* allow rule (`npm test && rm -rf /`), or hide a blocked binary behind a wrapper
|
|
11
|
+
* (`env X=1 rm -rf /`, `sudo rm -rf /`) or quoting.
|
|
12
|
+
*
|
|
13
|
+
* This module makes bash matching real. It is split in two concerns:
|
|
14
|
+
*
|
|
15
|
+
* 1. **A parser/classifier** — {@link parseBashCommand} splits a shell command
|
|
16
|
+
* string into its constituent *simple* commands across the control
|
|
17
|
+
* operators (`;`, `&&`, `||`, `|`, newlines) and `( … )` subshells /
|
|
18
|
+
* `$( … )` / `` ` … ` `` command substitutions, strips benign leading
|
|
19
|
+
* wrappers (`env VAR=x`, `nice`, `nohup`, `timeout N`, `xargs`, `command`,
|
|
20
|
+
* `builtin`, and `sudo`/`doas` — the last recorded as an *elevation* flag),
|
|
21
|
+
* and returns each base command + its raw argument string. Quoting is
|
|
22
|
+
* handled well enough that `"rm -rf /"` cannot hide behind quotes or a
|
|
23
|
+
* wrapper. The split is quote-aware so an operator *inside* a quoted string
|
|
24
|
+
* (`echo "a && b"`) is NOT treated as a command boundary.
|
|
25
|
+
*
|
|
26
|
+
* 2. **A catastrophic blocklist** — {@link isCatastrophicCommand} matches a
|
|
27
|
+
* single parsed sub-command against a conservative set of always-deny
|
|
28
|
+
* patterns (root-targeting `rm -rf`, fork bombs, `curl … | sh` of remote
|
|
29
|
+
* scripts, `dd of=/dev/…`, `mkfs`, `chmod -R 777 /`, redirect-to-raw-disk).
|
|
30
|
+
* It is deliberately narrow so normal dev commands (`rm -rf node_modules`,
|
|
31
|
+
* `rm -rf ./dist`, `curl … -o file`) are NOT blocked. This is
|
|
32
|
+
* defense-in-depth, NOT a hard security boundary — the OS sandbox and the
|
|
33
|
+
* permission gate are the real boundary.
|
|
34
|
+
*
|
|
35
|
+
* Everything here is pure, synchronous, and side-effect free so the classifier
|
|
36
|
+
* and the blocklist are exhaustively unit-testable, and the permission engine
|
|
37
|
+
* can fold them into its decision without any I/O.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* One simple command extracted from a (possibly compound) shell string.
|
|
41
|
+
*
|
|
42
|
+
* `rm -rf /tmp` parses to `{ name: "rm", args: ["-rf", "/tmp"], elevated: false,
|
|
43
|
+
* raw: "rm -rf /tmp" }`. The `name` is the base binary AFTER benign wrappers are
|
|
44
|
+
* stripped; `args` are its tokens (quotes removed); `raw` is the original
|
|
45
|
+
* sub-command slice (wrappers included) so a caller can echo exactly what the
|
|
46
|
+
* model asked for; `elevated` is true when a `sudo`/`doas` wrapper was stripped.
|
|
47
|
+
*/
|
|
48
|
+
export interface ParsedCommand {
|
|
49
|
+
/** The base binary, lower-cased for stable comparison (e.g. `"rm"`). */
|
|
50
|
+
readonly name: string;
|
|
51
|
+
/** The argument tokens after the binary, with surrounding quotes removed. */
|
|
52
|
+
readonly args: readonly string[];
|
|
53
|
+
/** Whether a `sudo`/`doas` elevation wrapper preceded this command. */
|
|
54
|
+
readonly elevated: boolean;
|
|
55
|
+
/** The original sub-command slice (wrappers included), trimmed. */
|
|
56
|
+
readonly raw: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Parse a (possibly compound) shell command string into the flat list of every
|
|
60
|
+
* simple command it runs — across operators, subshells, and command
|
|
61
|
+
* substitutions, with benign wrappers stripped.
|
|
62
|
+
*
|
|
63
|
+
* The result is order-preserving and exhaustive enough that the permission
|
|
64
|
+
* engine can evaluate a `Bash(...)` rule (and the catastrophic blocklist)
|
|
65
|
+
* against EACH sub-command independently: a deny on any sub-command denies the
|
|
66
|
+
* whole call, and an allow must cover every sub-command to auto-allow.
|
|
67
|
+
*
|
|
68
|
+
* @returns one {@link ParsedCommand} per simple command; an empty array for an
|
|
69
|
+
* empty/whitespace command string.
|
|
70
|
+
*/
|
|
71
|
+
export declare function parseBashCommand(command: string): ParsedCommand[];
|
|
72
|
+
/**
|
|
73
|
+
* Classify a SINGLE parsed sub-command against the catastrophic blocklist.
|
|
74
|
+
*
|
|
75
|
+
* @returns the byte-stable deny reason when the command is catastrophic, else
|
|
76
|
+
* `undefined`. Use {@link evaluateCatastrophic} for a whole compound call (it
|
|
77
|
+
* also runs the cross-sub-command `curl | sh` correlation).
|
|
78
|
+
*/
|
|
79
|
+
export declare function catastrophicReason(cmd: ParsedCommand): string | undefined;
|
|
80
|
+
/** Convenience boolean form of {@link catastrophicReason}. */
|
|
81
|
+
export declare function isCatastrophicCommand(cmd: ParsedCommand): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Evaluate the catastrophic blocklist over a WHOLE bash command string.
|
|
84
|
+
*
|
|
85
|
+
* Parses the (possibly compound) command, runs the per-sub-command blocklist on
|
|
86
|
+
* each leaf, AND runs the cross-sub-command `curl | sh` correlation. Returns a
|
|
87
|
+
* byte-stable deny message when ANY check fires, else `undefined`.
|
|
88
|
+
*
|
|
89
|
+
* This is what the permission engine calls for the bash tool: a catastrophic
|
|
90
|
+
* command is denied REGARDLESS of allow/ask/deny rules or mode (it is not even
|
|
91
|
+
* `bypass`-able, mirroring the original always-block guard).
|
|
92
|
+
*/
|
|
93
|
+
export declare function evaluateCatastrophic(command: string): string | undefined;
|
|
94
|
+
/**
|
|
95
|
+
* Render the rule-matching SUBJECT for each sub-command of a bash call: the base
|
|
96
|
+
* command joined with its argument tokens (wrappers stripped). The permission
|
|
97
|
+
* engine matches an induscode `Bash(...)` specifier against EACH of these — an
|
|
98
|
+
* allow rule must cover them all to auto-allow, a deny on any one denies the
|
|
99
|
+
* whole call.
|
|
100
|
+
*
|
|
101
|
+
* For `npm test && rm important.txt` this yields `["npm test", "rm
|
|
102
|
+
* important.txt"]`, so `Bash(npm test)` allows the first but not the second.
|
|
103
|
+
* Returns the original command (single element) when nothing parses, so a tool
|
|
104
|
+
* call always has at least one subject to match against.
|
|
105
|
+
*/
|
|
106
|
+
export declare function bashSubcommandSubjects(command: string): string[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bash command parser/classifier + catastrophic blocklist — unit tests.
|
|
3
|
+
*
|
|
4
|
+
* Pins the parser ({@link parseBashCommand}) across the four concerns that make
|
|
5
|
+
* coarse whole-string matching trivially bypassable:
|
|
6
|
+
* - **compound** splitting on `;`, `&&`, `||`, `|`, `&`, newlines,
|
|
7
|
+
* - benign **wrapper** stripping (`env VAR=x`, `nohup`, `timeout N`, `nice -n
|
|
8
|
+
* N`, `xargs`, `command`, and `sudo`/`doas` as elevation),
|
|
9
|
+
* - **subshell / command-substitution** descent (`( … )`, `$( … )`, `` ` … ` ``),
|
|
10
|
+
* - **quote-aware** tokenization so an operator inside a quote is not a
|
|
11
|
+
* boundary and a quoted/escaped binary cannot hide from the classifier,
|
|
12
|
+
* plus the documented bypass cases. Then the catastrophic blocklist
|
|
13
|
+
* ({@link evaluateCatastrophic}) — `rm -rf /` and root-ish targets, fork bombs,
|
|
14
|
+
* `curl | sh`, raw-disk writes, `mkfs`, `chmod -R 777 /` — alongside the
|
|
15
|
+
* negative cases proving normal dev commands are NOT blocked. No I/O.
|
|
16
|
+
*/
|
|
17
|
+
export {};
|
|
@@ -36,6 +36,7 @@ import { type ConductorFault, type ConductorPhase, type ConductorState, type Ses
|
|
|
36
36
|
import { ModelMatcher } from "./catalog";
|
|
37
37
|
import { SignalHub } from "./signal-hub";
|
|
38
38
|
import { TranscriptStore } from "./transcript-store";
|
|
39
|
+
import { DiagnosticsEngine, type DiagnosticsConfig } from "./diagnostics";
|
|
39
40
|
/**
|
|
40
41
|
* The slice of the framework `Agent` the conductor drives.
|
|
41
42
|
*
|
|
@@ -73,12 +74,30 @@ export interface AgentLike {
|
|
|
73
74
|
setThinkingLevel?(level: ThinkingLevel): void;
|
|
74
75
|
}
|
|
75
76
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
77
|
+
* Options handed to a {@link CondenseFn} on each invocation.
|
|
78
|
+
*
|
|
79
|
+
* Every field is optional so a scripted/no-op hook can ignore the bag entirely:
|
|
80
|
+
* - `force` — a manual `/compact` (fold to the last user turn) vs the
|
|
81
|
+
* budget-gated auto path.
|
|
82
|
+
* - `model` — the model bound to the live session, forwarded so the
|
|
83
|
+
* summarizer can write a *real* digest instead of the
|
|
84
|
+
* no-model local stub. Omitted when no model is bound.
|
|
85
|
+
* - `contextTokens` — the conductor's latest provider-reported context
|
|
86
|
+
* occupancy, an optional anchor for the slice planner.
|
|
87
|
+
*/
|
|
88
|
+
export interface CondenseOpts {
|
|
89
|
+
readonly force?: boolean;
|
|
90
|
+
readonly model?: Model<any>;
|
|
91
|
+
readonly contextTokens?: number;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The pluggable condense hook. Given the active branch's messages (and an options
|
|
95
|
+
* bag), it returns the (smaller) message list to replace it with. The live
|
|
96
|
+
* window-budget engine is wired in `boot/runners/session.ts`; the
|
|
97
|
+
* {@link noopCondense} default is an identity transform that ignores the opts.
|
|
79
98
|
*/
|
|
80
|
-
export type CondenseFn = (messages: AgentMessage[],
|
|
81
|
-
/** The default condense hook: returns the input unchanged (no-op). */
|
|
99
|
+
export type CondenseFn = (messages: AgentMessage[], opts?: CondenseOpts) => AgentMessage[] | Promise<AgentMessage[]>;
|
|
100
|
+
/** The default condense hook: returns the input unchanged (no-op), ignoring opts. */
|
|
82
101
|
export declare const noopCondense: CondenseFn;
|
|
83
102
|
/** Tuning for the transient-fault auto-retry. */
|
|
84
103
|
export interface RetryPolicy {
|
|
@@ -105,10 +124,22 @@ export interface ConductorDeps {
|
|
|
105
124
|
readonly condense?: CondenseFn;
|
|
106
125
|
/** Auto-retry tuning. */
|
|
107
126
|
readonly retry?: RetryPolicy;
|
|
108
|
-
/**
|
|
127
|
+
/**
|
|
128
|
+
* Legacy branch-length condense threshold. The auto path is now token-gated
|
|
129
|
+
* ({@link isOverBudget}), so this field is accepted but no longer governs when
|
|
130
|
+
* auto-compaction fires; kept for back-compat with callers that pass it.
|
|
131
|
+
*/
|
|
109
132
|
readonly compactAt?: number;
|
|
110
133
|
/** Sleep primitive (injected so tests don't actually wait). */
|
|
111
134
|
readonly sleep?: (ms: number) => Promise<void>;
|
|
135
|
+
/**
|
|
136
|
+
* Post-edit live-diagnostics control. Pass a ready {@link DiagnosticsEngine} to
|
|
137
|
+
* inject one directly (tests), a {@link DiagnosticsConfig} to tune the default
|
|
138
|
+
* engine, or omit it to get the default: ON when `workspace` has a
|
|
139
|
+
* `tsconfig.json` (eslint runner added when an eslint config is present),
|
|
140
|
+
* otherwise inert. Set `{ enabled: false }` to opt out entirely.
|
|
141
|
+
*/
|
|
142
|
+
readonly diagnostics?: DiagnosticsEngine | DiagnosticsConfig;
|
|
112
143
|
}
|
|
113
144
|
/** The transitions the reducer understands — fresh vocabulary, all typed. */
|
|
114
145
|
type StateAction = {
|