indusagi-coding-agent 0.2.3 → 0.2.4
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 +25 -0
- package/dist/entry.js +1733 -572
- package/dist/guardrails.js +21 -6
- package/dist/index.js +1687 -552
- package/dist/types/boot/heap.d.ts +31 -0
- package/dist/types/boot/runners/delegate-runner.d.ts +21 -1
- package/dist/types/boot/runners/server-mode.d.ts +71 -0
- package/dist/types/boot/runners/session.d.ts +24 -1
- package/dist/types/boot/runners/session.test.d.ts +6 -1
- package/dist/types/boot/server-token.d.ts +97 -0
- package/dist/types/capability-deck/contract.d.ts +7 -6
- package/dist/types/conductor/conductor.d.ts +24 -0
- package/dist/types/conductor/contract.d.ts +71 -7
- package/dist/types/conductor/index.d.ts +3 -3
- package/dist/types/conductor/permissions.d.ts +74 -4
- package/dist/types/conductor/post-edit-diagnostics.test.d.ts +8 -3
- package/dist/types/conductor/quota-error.d.ts +35 -0
- package/dist/types/console/auth-status.d.ts +28 -0
- package/dist/types/console/components/TerminalConsole.d.ts +0 -5
- package/dist/types/console/contract.d.ts +13 -0
- package/dist/types/console/input/index.d.ts +1 -1
- package/dist/types/console/input/keymap.d.ts +1 -1
- package/dist/types/console/input/paste.d.ts +53 -1
- package/dist/types/console/overlays/approval-queue.d.ts +18 -1
- package/dist/types/console/overlays/index.d.ts +1 -1
- package/dist/types/launch/login.d.ts +68 -0
- package/dist/types/launch/oauth.test.d.ts +20 -0
- package/dist/types/window-budget/summarize/condense.d.ts +6 -0
- package/dist/types/workflow-engine/agent-runner.d.ts +20 -1
- package/dist/types/workspace/brand.d.ts +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heap headroom guard.
|
|
3
|
+
*
|
|
4
|
+
* Node's default old-space cap (~2 GB on 64-bit builds) is too small for heavy
|
|
5
|
+
* runs — most acutely a burst of parallel sub-agents whose combined transcripts
|
|
6
|
+
* push the resident heap past the default and abort the whole process with a V8
|
|
7
|
+
* "JavaScript heap out of memory" fatal (the exact crash this guards against).
|
|
8
|
+
*
|
|
9
|
+
* `--max-old-space-size` can only be set at process launch, so before any real
|
|
10
|
+
* work begins we re-exec the *same* program once with a raised cap, inherit its
|
|
11
|
+
* stdio (so the interactive TUI runs unchanged in the child), and adopt its exit
|
|
12
|
+
* code. The re-exec is one level deep: the child carries `INDUS_HEAP_BOOSTED=1`
|
|
13
|
+
* so it skips the guard and runs the agent directly.
|
|
14
|
+
*
|
|
15
|
+
* Controlled by env:
|
|
16
|
+
* INDUS_MAX_HEAP_MB target old-space size in MB (default 4096; 0 disables
|
|
17
|
+
* the guard entirely and runs in-process).
|
|
18
|
+
* INDUS_HEAP_BOOSTED set internally on the child; never set this yourself.
|
|
19
|
+
*
|
|
20
|
+
* If the re-exec cannot spawn (a locked-down sandbox, a missing argv[1]) the
|
|
21
|
+
* guard silently falls back to running in the current, un-boosted process rather
|
|
22
|
+
* than failing to start.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Ensure the process has heap headroom, re-execing once if it does not.
|
|
26
|
+
*
|
|
27
|
+
* Returns normally in three cases: the guard is disabled, the cap is already
|
|
28
|
+
* raised, or the re-exec could not be spawned. Otherwise it does NOT return —
|
|
29
|
+
* it runs the boosted child to completion and exits with the child's status.
|
|
30
|
+
*/
|
|
31
|
+
export declare function ensureHeapHeadroom(): void;
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* The `spawn`/`tools` options are pure test seams — they let a unit test drive
|
|
26
26
|
* the runner with an in-memory fake instead of a real network round-trip.
|
|
27
27
|
*/
|
|
28
|
-
import { type AgentMessage } from "../../conductor";
|
|
28
|
+
import { type AgentMessage, type SessionPermissionPolicy } from "../../conductor";
|
|
29
29
|
import { type AgentTool } from "../../capability-deck";
|
|
30
30
|
import type { DelegateRunner } from "../../capability-deck/cards/task-card";
|
|
31
31
|
/**
|
|
@@ -75,6 +75,26 @@ export interface DelegateRunnerOptions {
|
|
|
75
75
|
* `task` card — the recursion guard).
|
|
76
76
|
*/
|
|
77
77
|
readonly tools?: () => AgentTool[];
|
|
78
|
+
/**
|
|
79
|
+
* Resolve the current server-tier gateway routing map (provider -> gateway
|
|
80
|
+
* base url). Called FRESH on every `run()` — never cached — so a mid-session
|
|
81
|
+
* `/login` (to "Indus Server"), including one that lands after this runner was
|
|
82
|
+
* constructed but before a given delegated call, is always picked up. This
|
|
83
|
+
* mirrors {@link getApiKey}, which is likewise invoked per-request rather than
|
|
84
|
+
* resolved once at construction.
|
|
85
|
+
*/
|
|
86
|
+
readonly getGatewayBaseUrls?: () => Promise<Record<string, string>>;
|
|
87
|
+
/**
|
|
88
|
+
* The parent session's live permission policy: the SAME mutable rule list the
|
|
89
|
+
* parent gate reads plus a live getter onto the conductor's current mode. When
|
|
90
|
+
* present, every spawned sub-agent runs under a RESOLVER-LESS gate built from
|
|
91
|
+
* it — deny/ask rules, plan-mode enforcement, and the catastrophic-bash
|
|
92
|
+
* blocklist apply per inner tool call, and a mid-run mode switch (Shift+Tab)
|
|
93
|
+
* retargets the very next delegated tool call. A sub-agent cannot prompt, so
|
|
94
|
+
* an `ask` decision deterministically denies with an actionable message.
|
|
95
|
+
* Absent (a bare test construction), the sub-agent runs ungated as before.
|
|
96
|
+
*/
|
|
97
|
+
readonly permissionPolicy?: SessionPermissionPolicy;
|
|
78
98
|
}
|
|
79
99
|
/**
|
|
80
100
|
* Build a live {@link DelegateRunner} the host wires into the deck context.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boot helper: decide whether a provider runs in **server mode** (Case B).
|
|
3
|
+
*
|
|
4
|
+
* A model turn takes one of three paths (see LOGIN_GATEWAY_PLAN.md §1):
|
|
5
|
+
* - **A. Local key** — a usable key for the provider is stored in the on-disk
|
|
6
|
+
* auth vault (or exported in the environment). The framework calls the
|
|
7
|
+
* provider directly; behaviour is unchanged and this module returns
|
|
8
|
+
* `{ serverMode: false }`.
|
|
9
|
+
* - **B. Server tier** — the provider is gateway-eligible, the user has **no**
|
|
10
|
+
* usable local key, and they hold a **valid server token**. The session token
|
|
11
|
+
* is vended as the provider "key" and the model is re-routed through the indus
|
|
12
|
+
* gateway. This module returns `{ serverMode: true, token }`.
|
|
13
|
+
* - **C. Blocked** — no local key and no valid token: `{ serverMode: false }`
|
|
14
|
+
* (the resolver then falls through to the framework's own env lookup, which
|
|
15
|
+
* surfaces the usual "no API key" guidance pointing at `indus login`).
|
|
16
|
+
*
|
|
17
|
+
* The two consumers in {@link file://./session.ts} call this with the SAME
|
|
18
|
+
* `(ctx, provider, vault)` triple so the key-resolver fallback and the
|
|
19
|
+
* gateway-base-url computation always agree on whether server mode is active.
|
|
20
|
+
*/
|
|
21
|
+
import type { BootContext } from "../contract";
|
|
22
|
+
import type { AuthVault } from "../../launch/contract";
|
|
23
|
+
/**
|
|
24
|
+
* The outcome of {@link resolveServerMode}: whether the provider should route
|
|
25
|
+
* through the gateway this run, and (when so) the session token to hand back as
|
|
26
|
+
* the provider api key.
|
|
27
|
+
*/
|
|
28
|
+
export interface ServerModeResult {
|
|
29
|
+
/** `true` => bind the gateway base url and use {@link token} as the key. */
|
|
30
|
+
serverMode: boolean;
|
|
31
|
+
/** The session token (`access_token`) to vend as the key, when `serverMode`. */
|
|
32
|
+
token?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Whether `provider` may be routed through the indus gateway. Excludes the
|
|
36
|
+
* non-portable providers (vertex / bedrock / codex) and the deferred opencode
|
|
37
|
+
* `anthropic-messages` api per the §B.4 caveat.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isGatewayEligibleProvider(provider: string): boolean;
|
|
40
|
+
/** The server-tier provider slugs (for default-model selection / listing). */
|
|
41
|
+
export declare function serverTierProviders(): readonly string[];
|
|
42
|
+
/**
|
|
43
|
+
* Decide whether `provider` runs in server mode for this session.
|
|
44
|
+
*
|
|
45
|
+
* Server mode is active when ALL hold:
|
|
46
|
+
* 1. {@link isGatewayEligibleProvider}(provider) — excludes vertex/bedrock/codex
|
|
47
|
+
* and the deferred opencode `anthropic-messages` api (§B.4);
|
|
48
|
+
* 2. no EXPLICIT local key — the vault holds no usable key for the provider
|
|
49
|
+
* (an ambient env var is intentionally ignored for server-tier providers);
|
|
50
|
+
* 3. {@link hasValidServerToken}() — a stored, non-expired server token exists.
|
|
51
|
+
*
|
|
52
|
+
* Returns `{ serverMode: false }` whenever any condition fails, so the
|
|
53
|
+
* local-key (Case A) path is preserved byte-for-byte: the resolver then returns
|
|
54
|
+
* `undefined` and no gateway base url is bound.
|
|
55
|
+
*
|
|
56
|
+
* @param _ctx the boot context (reserved for future per-run policy; the vault is
|
|
57
|
+
* passed explicitly so callers share one instance)
|
|
58
|
+
* @param provider the framework provider slug to evaluate
|
|
59
|
+
* @param vault the on-disk credential vault to probe for a local key
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolveServerMode(_ctx: BootContext, provider: string, vault: AuthVault): Promise<ServerModeResult>;
|
|
62
|
+
/**
|
|
63
|
+
* Build the per-provider gateway base-url map for THIS session: for every
|
|
64
|
+
* gateway-eligible provider currently in server mode (no explicit local key +
|
|
65
|
+
* valid token), map `provider -> ${INDUS_SERVER_URL}/gateway/${provider}`.
|
|
66
|
+
*
|
|
67
|
+
* The conductor consults this map at EVERY model bind — including a runtime
|
|
68
|
+
* `/model` switch — so selecting ANY server-tier model (not just the one the
|
|
69
|
+
* session launched on) routes through the gateway. Empty when nothing qualifies.
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolveServerGatewayUrls(ctx: BootContext, vault: AuthVault): Promise<Record<string, string>>;
|
|
@@ -8,7 +8,7 @@
|
|
|
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 AgentTool, type SessionConductor } from "../../conductor";
|
|
11
|
+
import { type AgentTool, type ApprovalResolver, type CanUseToolFn, type PermissionMode, type PermissionRule, type SessionConductor } from "../../conductor";
|
|
12
12
|
import { type MemoryStore } from "../../capability-deck";
|
|
13
13
|
import { type DelegateRunner } from "../../capability-deck/cards/task-card";
|
|
14
14
|
import type { WorkflowAgentRunner } from "../../workflow-engine";
|
|
@@ -54,6 +54,29 @@ export declare function composeSystem(tools: AgentTool[], inv: Invocation, cwd:
|
|
|
54
54
|
* @param cwd the run's working directory
|
|
55
55
|
*/
|
|
56
56
|
export declare function sessionScopeDir(sessionsRoot: string, cwd: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* Build the `canUseTool` gate factory for a boot run.
|
|
59
|
+
*
|
|
60
|
+
* The factory receives a getter onto the conductor's LIVE permission mode plus the
|
|
61
|
+
* conductor's stable approval delegate, and returns the gate that reads them. The
|
|
62
|
+
* delegate is threaded straight into `requestApproval`: on a NON-interactive boot
|
|
63
|
+
* (oneshot / link) no resolver is ever installed, so the delegate denies and an
|
|
64
|
+
* `ask` decision deterministically denies with an actionable message. The
|
|
65
|
+
* interactive (React) console installs its overlay resolver via the conductor's
|
|
66
|
+
* `setApprovalResolver` AFTER mount, at which point the SAME delegate begins
|
|
67
|
+
* routing `ask` decisions to the overlay — no rebuild, fully additive.
|
|
68
|
+
*
|
|
69
|
+
* `rules` is the session's LIVE mutable rule list: the gate re-scans it on every
|
|
70
|
+
* call, and an `allow-always` approval appends into it (via `appendAllowRule`
|
|
71
|
+
* below), so the approval is remembered for the remainder of the session — and
|
|
72
|
+
* ONLY the session; nothing is ever written back to settings. Every gate the
|
|
73
|
+
* factory mints (and every sub-agent gate built over the same array) sees the
|
|
74
|
+
* appended rules immediately.
|
|
75
|
+
*
|
|
76
|
+
* Exported for the boot tests; product callers go through
|
|
77
|
+
* {@link buildSessionConductor}.
|
|
78
|
+
*/
|
|
79
|
+
export declare function buildPermissionGate(rules: PermissionRule[], mode: PermissionMode, tools: readonly AgentTool[], planReachable?: boolean): ((currentMode: () => PermissionMode, requestApproval?: ApprovalResolver) => CanUseToolFn) | undefined;
|
|
57
80
|
export declare function buildSessionConductor(ctx: BootContext): Promise<SessionConductor>;
|
|
58
81
|
/**
|
|
59
82
|
* The prompts a oneshot run submits: the positional first prompt when present,
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Skills-surface wiring (#7 / fixes #11).
|
|
2
|
+
* Skills-surface wiring (#7 / fixes #11) + the boot permission-gate factory.
|
|
3
3
|
*
|
|
4
4
|
* Proves the chain that makes on-disk `SKILL.md` cards visible to the model:
|
|
5
5
|
* `gatherModelSkills` walks `cwd/.indusagi/skills`, drops any card flagged
|
|
6
6
|
* `disable-model-invocation`, and the survivors render into the briefing's
|
|
7
7
|
* `<available_skills>` block via `composeSystem`. A `--system` override must NOT
|
|
8
8
|
* carry the skills block (it replaces the whole prompt).
|
|
9
|
+
*
|
|
10
|
+
* `buildPermissionGate` is pinned here too: default mode ALWAYS builds a gate
|
|
11
|
+
* (only a rule-less bypass boot with plan unreachable goes gate-free), and the
|
|
12
|
+
* factory wires `appendAllowRule` into the live rule list so an `allow-always`
|
|
13
|
+
* approval is remembered for the remainder of the session (B2).
|
|
9
14
|
*/
|
|
10
15
|
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-token store — the boot-layer persistence for the indus device-login
|
|
3
|
+
* session token. This is the app side of the login + model-gateway contract: a
|
|
4
|
+
* successful `indus login` device flow writes the better-auth session token
|
|
5
|
+
* here, and the key resolver later reads it back to drive "server mode" (Case B
|
|
6
|
+
* — no local provider key, but a valid server session, so requests are routed
|
|
7
|
+
* through the indus-server gateway with the session token as the api key).
|
|
8
|
+
*
|
|
9
|
+
* The token lives in a single JSON file under the app's profile directory
|
|
10
|
+
* (`~/.indusagi/agent/server-token.json`) — deliberately NOT the framework's
|
|
11
|
+
* `~/.better-auth` location, so the CLI session is isolated from any other
|
|
12
|
+
* tooling. As with the credential vault, the file is tiny and access is
|
|
13
|
+
* interactive, so we read / write the whole file each time and tolerate a
|
|
14
|
+
* missing or malformed file as "no token".
|
|
15
|
+
*
|
|
16
|
+
* Ported from supercli `server/src/lib/token.ts`; the load-bearing detail is the
|
|
17
|
+
* `expires_in` (seconds, from the device-flow response) → `expires_at` (absolute
|
|
18
|
+
* ISO timestamp, on disk) conversion, which lets {@link isServerTokenExpired}
|
|
19
|
+
* decide freshness without re-deriving the clock offset.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Root of the indus-server. Used to build the gateway base URL and the
|
|
23
|
+
* device-login endpoints. Any trailing slash(es) are stripped so callers can
|
|
24
|
+
* append paths with a single separator.
|
|
25
|
+
*/
|
|
26
|
+
export declare const INDUS_SERVER_URL: string;
|
|
27
|
+
/** The app profile directory that holds the server session token. */
|
|
28
|
+
export declare const CONFIG_DIR: string;
|
|
29
|
+
/** Absolute path of the JSON file the session token is persisted to. */
|
|
30
|
+
export declare const TOKEN_FILE: string;
|
|
31
|
+
/** On-disk token shape (what {@link storeServerToken} writes / {@link getServerToken} returns). */
|
|
32
|
+
export interface TokenData {
|
|
33
|
+
/** The better-auth session token, used as the gateway api key in server mode. */
|
|
34
|
+
access_token: string;
|
|
35
|
+
/** Optional refresh token, when the device flow returns one. */
|
|
36
|
+
refresh_token?: string;
|
|
37
|
+
/** Token scheme; defaults to `"Bearer"`. */
|
|
38
|
+
token_type: string;
|
|
39
|
+
/** Optional granted scope string. */
|
|
40
|
+
scope?: string;
|
|
41
|
+
/** Absolute expiry as an ISO string, or null when the token has no expiry. */
|
|
42
|
+
expires_at: string | null;
|
|
43
|
+
/** When this record was written, as an ISO string. */
|
|
44
|
+
created_at: string;
|
|
45
|
+
}
|
|
46
|
+
/** Device-flow response shape accepted by {@link storeServerToken} (`expires_in` → `expires_at`). */
|
|
47
|
+
export interface ServerTokenInput {
|
|
48
|
+
/** The better-auth session token. */
|
|
49
|
+
access_token: string;
|
|
50
|
+
/** Optional refresh token. */
|
|
51
|
+
refresh_token?: string;
|
|
52
|
+
/** Token scheme; defaults to `"Bearer"` when omitted. */
|
|
53
|
+
token_type?: string;
|
|
54
|
+
/** Optional granted scope string. */
|
|
55
|
+
scope?: string;
|
|
56
|
+
/** Lifetime in seconds, relative to now; converted to an absolute `expires_at`. */
|
|
57
|
+
expires_in?: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Read the stored session token, tolerating a missing or malformed file as
|
|
61
|
+
* "no token".
|
|
62
|
+
*
|
|
63
|
+
* @returns the parsed {@link TokenData}, or `null` when nothing is stored.
|
|
64
|
+
*/
|
|
65
|
+
export declare function getServerToken(): Promise<TokenData | null>;
|
|
66
|
+
/**
|
|
67
|
+
* Persist a session token from a device-flow response, converting the relative
|
|
68
|
+
* `expires_in` (seconds) into an absolute `expires_at` ISO timestamp and
|
|
69
|
+
* defaulting `token_type` to `"Bearer"`. Creates the profile directory if
|
|
70
|
+
* needed and writes the file with owner-only permissions.
|
|
71
|
+
*
|
|
72
|
+
* @param token the device-flow token payload.
|
|
73
|
+
* @returns `true` on success, `false` if the write failed.
|
|
74
|
+
*/
|
|
75
|
+
export declare function storeServerToken(token: ServerTokenInput): Promise<boolean>;
|
|
76
|
+
/**
|
|
77
|
+
* Remove the stored session token (used by `indus logout`). A missing file is
|
|
78
|
+
* treated as nothing-to-do.
|
|
79
|
+
*
|
|
80
|
+
* @returns `true` when a file was deleted, `false` when none existed / removal failed.
|
|
81
|
+
*/
|
|
82
|
+
export declare function clearServerToken(): Promise<boolean>;
|
|
83
|
+
/**
|
|
84
|
+
* Decide whether a token is expired. A token counts as expired when it is
|
|
85
|
+
* absent, has no `expires_at`, or has under 5 minutes of life remaining (the
|
|
86
|
+
* margin keeps a long turn from racing the expiry boundary).
|
|
87
|
+
*
|
|
88
|
+
* @param token the token to inspect (or `null`).
|
|
89
|
+
* @returns `true` when the token is missing or stale.
|
|
90
|
+
*/
|
|
91
|
+
export declare function isServerTokenExpired(token: TokenData | null): boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Convenience predicate: there is a stored token and it is not expired.
|
|
94
|
+
*
|
|
95
|
+
* @returns `true` when a usable (fresh) session token is on disk.
|
|
96
|
+
*/
|
|
97
|
+
export declare function hasValidServerToken(): Promise<boolean>;
|
|
@@ -124,12 +124,13 @@ export type CardProfiles = readonly DeckProfile[];
|
|
|
124
124
|
* The named capability sets a session can be provisioned with — the profile
|
|
125
125
|
* table that replaces a trio of near-identical build functions.
|
|
126
126
|
*
|
|
127
|
-
* - `authoring` — the
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* - `all` — every registered capability, nothing withheld.
|
|
127
|
+
* - `authoring` — the READ-ONLY built-in subset: read/ls/search/web/
|
|
128
|
+
* checklist-read. No filesystem mutation, no shell, no app-novel cards —
|
|
129
|
+
* the deck sub-agents (task/workflow) are provisioned with (see
|
|
130
|
+
* {@link "./provision"}'s PROFILE_TABLE, the authoritative mapping).
|
|
131
|
+
* - `survey` — every built-in (mutating ones included), no app-novel cards.
|
|
132
|
+
* - `all` — every registered capability, nothing withheld. What an
|
|
133
|
+
* interactive coding session runs with.
|
|
133
134
|
*/
|
|
134
135
|
export type DeckProfile = "authoring" | "survey" | "all";
|
|
135
136
|
/**
|
|
@@ -89,6 +89,18 @@ export interface CondenseOpts {
|
|
|
89
89
|
readonly force?: boolean;
|
|
90
90
|
readonly model?: Model<any>;
|
|
91
91
|
readonly contextTokens?: number;
|
|
92
|
+
/**
|
|
93
|
+
* Cancellation for a manual `/compact`. A hook MAY honor it mid-summarization;
|
|
94
|
+
* the conductor always checks it after the hook returns and discards the
|
|
95
|
+
* result when aborted, so even a hook that ignores the signal cancels cleanly.
|
|
96
|
+
*/
|
|
97
|
+
readonly signal?: AbortSignal;
|
|
98
|
+
/**
|
|
99
|
+
* The bound model's credential, resolved through the session's per-call key
|
|
100
|
+
* resolver. Forwarded so the summarizer authenticates exactly like a turn —
|
|
101
|
+
* server-tier sessions have no env-var fallback for this.
|
|
102
|
+
*/
|
|
103
|
+
readonly apiKey?: string;
|
|
92
104
|
}
|
|
93
105
|
/**
|
|
94
106
|
* The pluggable condense hook. Given the active branch's messages (and an options
|
|
@@ -186,4 +198,16 @@ export declare function reduceState(prev: ConductorState, action: StateAction):
|
|
|
186
198
|
* @param deps injectable collaborators; all optional, live defaults supplied
|
|
187
199
|
*/
|
|
188
200
|
export declare function createSessionConductor(options: SessionConductorOptions, deps?: ConductorDeps): SessionConductor;
|
|
201
|
+
/**
|
|
202
|
+
* Return the framework model unchanged, or — when a gateway base URL is set
|
|
203
|
+
* ("server mode") — a CLONE with its `baseUrl` swapped to the gateway so the
|
|
204
|
+
* request flows through the quota-enforcing indus-server. Never mutates the
|
|
205
|
+
* shared catalog card (always spreads into a fresh object). When
|
|
206
|
+
* `gatewayBaseUrl` is `undefined` the model is returned by reference, keeping
|
|
207
|
+
* the local-key path byte-for-byte unchanged.
|
|
208
|
+
*/
|
|
209
|
+
export declare function withGatewayBaseUrl<M extends {
|
|
210
|
+
baseUrl: string;
|
|
211
|
+
provider: string;
|
|
212
|
+
}>(model: M, gatewayBaseUrls: Record<string, string> | undefined): M;
|
|
189
213
|
export {};
|
|
@@ -103,8 +103,14 @@ export declare function conductorFault(kind: FaultKind, message: string, cause?:
|
|
|
103
103
|
* - `tool_end` — a tool invocation finished (`ok` = no error).
|
|
104
104
|
* - `turn_end` — the assistant turn settled; `usage` reports token spend.
|
|
105
105
|
* - `persisted` — the latest node was committed to the transcript (`entryId`).
|
|
106
|
-
* - `compacted` — the transcript
|
|
107
|
-
*
|
|
106
|
+
* - `compacted` — the transcript WAS condensed (emitted on completion, not at
|
|
107
|
+
* the start). `manual` marks a user-driven `/compact` — a view may then
|
|
108
|
+
* reset its visible transcript — versus mid-turn auto-compaction, where the
|
|
109
|
+
* display must keep the in-flight exchange on screen.
|
|
110
|
+
* - `fault` — a typed {@link ConductorFault} occurred. `transient` marks a
|
|
111
|
+
* fault surfaced purely as an in-turn notice (e.g. a fallback-model swap on
|
|
112
|
+
* provider overload) — the turn keeps running, so a consumer must NOT treat
|
|
113
|
+
* it as the turn's end (must not clear a busy/in-flight indicator on it).
|
|
108
114
|
* - `queue` — the pending-input queue changed; `count` is its new depth.
|
|
109
115
|
* - `idle` — the conductor has no in-flight work and is ready for input.
|
|
110
116
|
*/
|
|
@@ -138,9 +144,11 @@ export type SessionSignal = {
|
|
|
138
144
|
readonly entryId: string;
|
|
139
145
|
} | {
|
|
140
146
|
readonly kind: "compacted";
|
|
147
|
+
readonly manual?: boolean;
|
|
141
148
|
} | {
|
|
142
149
|
readonly kind: "fault";
|
|
143
150
|
readonly fault: ConductorFault;
|
|
151
|
+
readonly transient?: boolean;
|
|
144
152
|
} | {
|
|
145
153
|
readonly kind: "queue";
|
|
146
154
|
readonly count: number;
|
|
@@ -428,6 +436,18 @@ export interface SessionConductorOptions {
|
|
|
428
436
|
* the swap entirely (behavior-preserving default).
|
|
429
437
|
*/
|
|
430
438
|
readonly fallbackModelId?: string;
|
|
439
|
+
/**
|
|
440
|
+
* Per-provider indus-gateway base URLs ("server mode"), keyed by provider slug
|
|
441
|
+
* (e.g. `{ minimax: "http://host/gateway/minimax", sarvam: "…/gateway/sarvam" }`).
|
|
442
|
+
* Set by the session runner for every gateway-eligible provider the user has no
|
|
443
|
+
* local key for but holds a valid server token. At EVERY model bind (including a
|
|
444
|
+
* runtime `/model` switch) the conductor looks up the BOUND model's provider in
|
|
445
|
+
* this map and, when present, binds a CLONE of the framework model with its
|
|
446
|
+
* `baseUrl` swapped to that URL — so any server-tier model routes through the
|
|
447
|
+
* quota-enforcing server while the session token (via {@link getApiKey})
|
|
448
|
+
* authenticates it. A provider absent from the map keeps the direct path.
|
|
449
|
+
*/
|
|
450
|
+
readonly gatewayBaseUrls?: Record<string, string>;
|
|
431
451
|
/** Working directory the session is scoped to (defaults to process cwd). */
|
|
432
452
|
readonly workspace?: string;
|
|
433
453
|
/**
|
|
@@ -496,6 +516,11 @@ export interface SessionConductorOptions {
|
|
|
496
516
|
*/
|
|
497
517
|
readonly checkpoint?: CheckpointPort;
|
|
498
518
|
}
|
|
519
|
+
/**
|
|
520
|
+
* What a manual {@link SessionConductor.condense} run amounted to — the honest
|
|
521
|
+
* completion vocabulary `/compact` reports from (see the method doc).
|
|
522
|
+
*/
|
|
523
|
+
export type CondenseOutcome = "condensed" | "nothing" | "cancelled" | "failed" | "busy";
|
|
499
524
|
/**
|
|
500
525
|
* The conductor of a single coding-agent session.
|
|
501
526
|
*
|
|
@@ -537,6 +562,15 @@ export interface SessionConductor {
|
|
|
537
562
|
pendingInputs(): readonly QueuedInput[];
|
|
538
563
|
/** Discard every queued input. Emits a `{ kind: "queue" }` signal. */
|
|
539
564
|
clearQueue(): void;
|
|
565
|
+
/**
|
|
566
|
+
* Promote the NEWEST queued input to run immediately: the in-flight turn (if
|
|
567
|
+
* any) is aborted — its work stops — while the rest of the queue is kept, and
|
|
568
|
+
* the promoted input runs as the very next turn. The user-facing "run my new
|
|
569
|
+
* message NOW" affordance for a long/stuck turn (issue #19), distinct from
|
|
570
|
+
* {@link abort} (which stops everything and clears the queue). Returns `false`
|
|
571
|
+
* when no input is queued.
|
|
572
|
+
*/
|
|
573
|
+
steerNow(): boolean;
|
|
540
574
|
/**
|
|
541
575
|
* Remove and return the text of the most-recently queued input, or `undefined`
|
|
542
576
|
* when the queue is empty. Lets a UI pop the last entry back into its prompt.
|
|
@@ -570,6 +604,20 @@ export interface SessionConductor {
|
|
|
570
604
|
* @param id canonical id of the model to switch to
|
|
571
605
|
*/
|
|
572
606
|
selectModel(id: string): void;
|
|
607
|
+
/**
|
|
608
|
+
* Replace the server-tier gateway routing map and immediately re-bind the
|
|
609
|
+
* currently selected model against it (without changing which model is
|
|
610
|
+
* selected). Lets an interactive mid-session sign-in (see `/login` ->
|
|
611
|
+
* "Indus Server") take effect right away: the gateway base-url map is
|
|
612
|
+
* otherwise frozen at conductor construction, so a login that happens after
|
|
613
|
+
* boot would silently never route the bound model through the gateway even
|
|
614
|
+
* though the per-request key resolver already started vending the fresh
|
|
615
|
+
* session token as the provider key.
|
|
616
|
+
*
|
|
617
|
+
* @param map provider id -> gateway base URL, as produced by
|
|
618
|
+
* `resolveServerGatewayUrls` for the current vault/token state
|
|
619
|
+
*/
|
|
620
|
+
updateGatewayBaseUrls(map: Record<string, string>): void;
|
|
573
621
|
/**
|
|
574
622
|
* Replace the agent's tool deck for subsequent turns.
|
|
575
623
|
*
|
|
@@ -584,11 +632,27 @@ export interface SessionConductor {
|
|
|
584
632
|
*/
|
|
585
633
|
registerTools(tools: AgentTool[]): void;
|
|
586
634
|
/**
|
|
587
|
-
* Manually run the
|
|
588
|
-
*
|
|
589
|
-
*
|
|
590
|
-
|
|
591
|
-
|
|
635
|
+
* Manually run the transcript-condense path (`/compact`) and report what
|
|
636
|
+
* happened, so the caller can give honest feedback instead of announcing
|
|
637
|
+
* "condensed" regardless:
|
|
638
|
+
* - `"condensed"` — the branch shrank and was rebound (emits `compacted`).
|
|
639
|
+
* - `"nothing"` — nothing older to fold (single-turn session, or already
|
|
640
|
+
* compacted); the transcript is untouched.
|
|
641
|
+
* - `"cancelled"` — {@link cancelCondense} fired mid-run; the digest was
|
|
642
|
+
* discarded and the transcript is untouched.
|
|
643
|
+
* - `"failed"` — the condense hook threw; a typed fault was emitted.
|
|
644
|
+
* - `"busy"` — a turn is in flight; compact after it settles (or abort
|
|
645
|
+
* it first).
|
|
646
|
+
* While the condense runs, {@link submit} queues instead of racing it, and the
|
|
647
|
+
* queue drains once the condense settles.
|
|
648
|
+
*/
|
|
649
|
+
condense(): Promise<CondenseOutcome>;
|
|
650
|
+
/**
|
|
651
|
+
* Cancel an in-flight manual {@link condense} (the `/compact` Esc affordance).
|
|
652
|
+
* The summarizer's result is discarded and the transcript stays untouched; a
|
|
653
|
+
* no-op when no manual condense is running.
|
|
654
|
+
*/
|
|
655
|
+
cancelCondense(): void;
|
|
592
656
|
/**
|
|
593
657
|
* Branch the transcript from a prior node. A new branch is opened whose parent
|
|
594
658
|
* is `entryId`; the agent's message list is rebound to that branch's root→leaf
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
* land; consumers import the conductor surface from `src/conductor` rather than
|
|
15
15
|
* reaching into individual modules.
|
|
16
16
|
*/
|
|
17
|
-
export type { FaultKind, ConductorFault, SessionSignal, SignalKind, SignalOf, SignalHandler, TranscriptSchema, TranscriptRole, TranscriptEntry, SessionHead, ModelCardRef, MatchQuery, ConductorPhase, ConductorState, QueueMode, QueuedInput, SessionStats, ExecuteBashOptions, BashOutcome, SessionConductor, SessionConductorOptions, AgentMessage, AgentTool, CanUseToolFn, ThinkingLevel, Model, Usage, KnownProvider, PermissionMode, } from "./contract";
|
|
17
|
+
export type { FaultKind, ConductorFault, SessionSignal, SignalKind, SignalOf, SignalHandler, TranscriptSchema, TranscriptRole, TranscriptEntry, SessionHead, ModelCardRef, MatchQuery, ConductorPhase, ConductorState, QueueMode, QueuedInput, SessionStats, ExecuteBashOptions, BashOutcome, SessionConductor, SessionConductorOptions, CondenseOutcome, AgentMessage, AgentTool, CanUseToolFn, ThinkingLevel, Model, Usage, KnownProvider, PermissionMode, } from "./contract";
|
|
18
18
|
export { conductorFault, TRANSCRIPT_SCHEMA } from "./contract";
|
|
19
|
-
export { createSessionConductor, reduceState, noopCondense, type AgentLike, type ConductorDeps, type CondenseFn, type RetryPolicy, } from "./conductor";
|
|
19
|
+
export { createSessionConductor, reduceState, noopCondense, withGatewayBaseUrl, type AgentLike, type ConductorDeps, type CondenseFn, type RetryPolicy, } from "./conductor";
|
|
20
20
|
export { SignalHub, translateAgentEvent, type SignalHubOptions } from "./signal-hub";
|
|
21
21
|
export { ModelCatalog, ModelMatcher, canonicalId, toCardRef, type CatalogCard, type CatalogSource, type ResolveInput, } from "./catalog";
|
|
22
22
|
export { TranscriptStore, memoryBackend, fsBackend, replay, type TranscriptBackend, type TranscriptState, type TranscriptClock, type TranscriptStoreOptions, } from "./transcript-store";
|
|
23
23
|
export { parseSkillInvocation, type SkillInvocation } from "./skill-parse";
|
|
24
|
-
export { resolveRuleDecision, parseRule, makeRule, toolMatchesRule, createPermissionGate, isReadOnlyToolName, isEditToolName, READ_ONLY_TOOL_NAMES, EDIT_TOOL_NAMES, type PermissionBehavior, type PermissionRule, type PermissionDecision, type CanUseToolFn as PermissionCanUseToolFn, type ApprovalChoice, type ApprovalResolver, type PermissionGateConfig, } from "./permissions";
|
|
24
|
+
export { resolveRuleDecision, parseRule, makeRule, toolMatchesRule, createPermissionGate, createSubagentPermissionGate, collectReadOnlyToolNames, allowAlwaysRuleStrings, isReadOnlyToolName, isEditToolName, READ_ONLY_TOOL_NAMES, EDIT_TOOL_NAMES, type PermissionBehavior, type PermissionRule, type PermissionDecision, type CanUseToolFn as PermissionCanUseToolFn, type ApprovalChoice, type ApprovalOutcome, type ApprovalResolver, type PermissionGateConfig, type SessionPermissionPolicy, } from "./permissions";
|
|
25
25
|
export { parseBashCommand, catastrophicReason, isCatastrophicCommand, evaluateCatastrophic, bashSubcommandSubjects, type ParsedCommand, } from "./bash-guard";
|
|
26
26
|
export { DiagnosticsEngine, createDiagnosticKey, deduplicateAndCap, formatDiagnosticsSummary, getSeveritySymbol, parseTscOutput, parseEslintOutput, runTscDiagnostics, runEslintDiagnostics, hasTsConfig, hasEslintConfig, isTypeScriptOrJs, areDiagnosticsEqual, MAX_PER_FILE, MAX_TOTAL, MAX_SUMMARY_CHARS, type Diagnostic, type DiagnosticFile, type DiagnosticSeverity, type DiagnosticsConfig, type DiagnosticRunner, type DedupOptions, type Position, } from "./diagnostics";
|
|
@@ -74,16 +74,29 @@ export type CanUseToolFn = (toolName: string, input: unknown, opts: {
|
|
|
74
74
|
* - `deny` — block this call.
|
|
75
75
|
*/
|
|
76
76
|
export type ApprovalChoice = "allow-once" | "allow-always" | "deny";
|
|
77
|
+
/**
|
|
78
|
+
* What an {@link ApprovalResolver} may resolve to: a user's {@link ApprovalChoice},
|
|
79
|
+
* or `"unavailable"` — the conductor's stable delegate answers that when NO
|
|
80
|
+
* interactive resolver is installed behind it (a headless boot, or the console not
|
|
81
|
+
* yet mounted). The gate maps `"unavailable"` to the actionable "requires
|
|
82
|
+
* approval" deny (pointing at `--permission-mode` / settings allow rules) rather
|
|
83
|
+
* than the "denied by the user" message, which would be false when no user ever
|
|
84
|
+
* saw a prompt.
|
|
85
|
+
*/
|
|
86
|
+
export type ApprovalOutcome = ApprovalChoice | "unavailable";
|
|
77
87
|
/**
|
|
78
88
|
* The OPTIONAL host approval resolver. When present, an `ask` decision awaits it;
|
|
79
89
|
* the host (an interactive overlay) returns the user's choice. It MUST resolve to
|
|
80
90
|
* `"deny"` on abort so a cancelled turn never hangs on a pending prompt. When the
|
|
81
91
|
* resolver is absent (non-interactive boot / oneshot / link), an `ask` decision
|
|
82
|
-
* deterministically denies.
|
|
92
|
+
* deterministically denies. A stable pass-through delegate (the conductor's)
|
|
93
|
+
* resolves `"unavailable"` while no real resolver is installed behind it, so the
|
|
94
|
+
* gate can surface the actionable non-interactive deny message instead of a
|
|
95
|
+
* fictitious user denial.
|
|
83
96
|
*/
|
|
84
97
|
export type ApprovalResolver = (toolName: string, input: unknown, opts: {
|
|
85
98
|
signal?: AbortSignal;
|
|
86
|
-
}) => Promise<
|
|
99
|
+
}) => Promise<ApprovalOutcome>;
|
|
87
100
|
/**
|
|
88
101
|
* Tool names known to only inspect state (never mutate). Mirrors the framework's
|
|
89
102
|
* `READ_ONLY_TOOL_NAMES`; matched case-insensitively. A tool the framework marks
|
|
@@ -172,6 +185,22 @@ export declare function toolMatchesRule(toolName: string, input: unknown, rule:
|
|
|
172
185
|
* Pure and total: no I/O, no async, deterministic for a given input.
|
|
173
186
|
*/
|
|
174
187
|
export declare function resolveRuleDecision(toolName: string, input: unknown, rules: readonly PermissionRule[], mode: PermissionMode, readOnlyExtra?: ReadonlySet<string>): PermissionBehavior;
|
|
188
|
+
/**
|
|
189
|
+
* The rule strings an `allow-always` approval mints — i.e. what the session will
|
|
190
|
+
* REMEMBER for this tool call.
|
|
191
|
+
*
|
|
192
|
+
* For the shell tool the remembered rules are scoped to the command: one
|
|
193
|
+
* `Bash(<sub-command>)` rule per constituent sub-command (so approving
|
|
194
|
+
* `git status && npm test` remembers both halves, and re-running either — or the
|
|
195
|
+
* same compound — auto-allows), but approving one command never whitelists the
|
|
196
|
+
* whole shell. Every other tool remembers the bare tool name (`Edit`), matching
|
|
197
|
+
* the prompt's "remember the tool for this session" copy.
|
|
198
|
+
*
|
|
199
|
+
* Exported so the approval overlay can show the user exactly what "Allow always"
|
|
200
|
+
* will remember (the `suggestions` line), and so the gate and the UI can never
|
|
201
|
+
* disagree about it.
|
|
202
|
+
*/
|
|
203
|
+
export declare function allowAlwaysRuleStrings(toolName: string, input: unknown): string[];
|
|
175
204
|
/** Configuration for {@link createPermissionGate}. */
|
|
176
205
|
export interface PermissionGateConfig {
|
|
177
206
|
/**
|
|
@@ -188,8 +217,11 @@ export interface PermissionGateConfig {
|
|
|
188
217
|
readonly requestApproval?: ApprovalResolver;
|
|
189
218
|
/**
|
|
190
219
|
* Append a session-scoped allow rule when the host returns `allow-always`. The
|
|
191
|
-
*
|
|
192
|
-
*
|
|
220
|
+
* boot layer owns the mutable rule list (the SAME array instance `rules` refers
|
|
221
|
+
* to) and passes a push here, so the appended rule is visible to every
|
|
222
|
+
* subsequent gate consultation within the session — and to every other gate
|
|
223
|
+
* built over the same list (sub-agent gates included). Session-scoped only:
|
|
224
|
+
* the rule is never persisted to settings.
|
|
193
225
|
*/
|
|
194
226
|
readonly appendAllowRule?: (rule: PermissionRule) => void;
|
|
195
227
|
/**
|
|
@@ -215,3 +247,41 @@ export interface PermissionGateConfig {
|
|
|
215
247
|
* The result is assignable to `indusagi/agent`'s `CanUseToolFn`.
|
|
216
248
|
*/
|
|
217
249
|
export declare function createPermissionGate(config: PermissionGateConfig): CanUseToolFn;
|
|
250
|
+
/**
|
|
251
|
+
* The live permission policy a session shares with its sub-agent runners (the
|
|
252
|
+
* `task` and `workflow` tools): the SAME mutable rule list the parent gate reads
|
|
253
|
+
* — so a session-scoped `allow-always` rule and the settings deny/ask rules are
|
|
254
|
+
* enforced inside delegations too — plus a live getter onto the parent
|
|
255
|
+
* conductor's permission mode, so a mid-run Shift+Tab (bypass → default, plan,
|
|
256
|
+
* back to bypass, …) retargets the very next sub-agent tool call.
|
|
257
|
+
*/
|
|
258
|
+
export interface SessionPermissionPolicy {
|
|
259
|
+
/** The session's ordered rule list — the live array, never a snapshot. */
|
|
260
|
+
readonly rules: readonly PermissionRule[];
|
|
261
|
+
/** Live getter onto the parent session's current permission mode. */
|
|
262
|
+
readonly mode: () => PermissionMode;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Collect the names of every tool flagged `readOnly: true` (structurally probed,
|
|
266
|
+
* so any deck/framework/MCP tool shape works). These names are auto-allowed by
|
|
267
|
+
* the gate in `default`/`acceptEdits`/`plan` beyond the static
|
|
268
|
+
* {@link READ_ONLY_TOOL_NAMES}.
|
|
269
|
+
*/
|
|
270
|
+
export declare function collectReadOnlyToolNames(tools: ReadonlyArray<{
|
|
271
|
+
readonly name: string;
|
|
272
|
+
}>): ReadonlySet<string>;
|
|
273
|
+
/**
|
|
274
|
+
* Build the RESOLVER-LESS gate a sub-agent runs under.
|
|
275
|
+
*
|
|
276
|
+
* A sub-agent cannot prompt the user, so no {@link ApprovalResolver} is wired:
|
|
277
|
+
* an `ask` decision deterministically denies with an actionable message, while
|
|
278
|
+
* deny rules, plan-mode enforcement, the catastrophic-bash blocklist, read-only
|
|
279
|
+
* auto-allow, and bypass/acceptEdits behaviour all apply per inner tool call —
|
|
280
|
+
* against the LIVE parent mode, so mid-run mode switches reach delegations too.
|
|
281
|
+
*
|
|
282
|
+
* @param policy the session's shared rules + live mode getter
|
|
283
|
+
* @param tools the sub-agent's actual deck, probed for `readOnly: true` flags
|
|
284
|
+
*/
|
|
285
|
+
export declare function createSubagentPermissionGate(policy: SessionPermissionPolicy, tools: ReadonlyArray<{
|
|
286
|
+
readonly name: string;
|
|
287
|
+
}>): CanUseToolFn;
|
|
@@ -6,8 +6,13 @@
|
|
|
6
6
|
* over a scripted in-memory runner (no real tsc spawn). Verifies:
|
|
7
7
|
* - a turn that introduces a NEW diagnostic enqueues the summary follow-up;
|
|
8
8
|
* - a clean edit (no new diagnostics) enqueues nothing;
|
|
9
|
-
* -
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* - a still-unfixed diagnostic is delivered AT MOST ONCE per session — a
|
|
10
|
+
* follow-up turn whose "fix" edits fail to eliminate the same error must
|
|
11
|
+
* NOT be re-prompted with it (the endless self-continuation of issue #15);
|
|
12
|
+
* - even genuinely NEW errors per fix attempt stop re-prompting at the
|
|
13
|
+
* consecutive follow-up cap, and a fresh user prompt re-arms the budget;
|
|
14
|
+
* - `idle` is not emitted between a turn and its auto-drained follow-up (the
|
|
15
|
+
* spinner must not drop while the agent keeps working);
|
|
16
|
+
* - a non-TS edit never triggers the checker.
|
|
12
17
|
*/
|
|
13
18
|
export {};
|