indusagi-coding-agent 0.2.2 → 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.
Files changed (59) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/entry.js +12434 -9445
  3. package/dist/guardrails.js +897 -37
  4. package/dist/index.js +14081 -11111
  5. package/dist/types/boot/heap.d.ts +31 -0
  6. package/dist/types/boot/runners/delegate-runner.d.ts +27 -1
  7. package/dist/types/boot/runners/server-mode.d.ts +71 -0
  8. package/dist/types/boot/runners/session.d.ts +26 -18
  9. package/dist/types/boot/runners/session.test.d.ts +6 -1
  10. package/dist/types/boot/server-token.d.ts +97 -0
  11. package/dist/types/capability-deck/cards/index.d.ts +1 -0
  12. package/dist/types/capability-deck/cards/task-card.d.ts +26 -2
  13. package/dist/types/capability-deck/cards/workflow-card.d.ts +55 -0
  14. package/dist/types/capability-deck/cards/workflow-card.test.d.ts +12 -0
  15. package/dist/types/capability-deck/contract.d.ts +7 -6
  16. package/dist/types/capability-deck/index.d.ts +1 -1
  17. package/dist/types/conductor/conductor.d.ts +24 -0
  18. package/dist/types/conductor/contract.d.ts +79 -7
  19. package/dist/types/conductor/index.d.ts +3 -3
  20. package/dist/types/conductor/permissions.d.ts +74 -4
  21. package/dist/types/conductor/post-edit-diagnostics.test.d.ts +8 -3
  22. package/dist/types/conductor/quota-error.d.ts +35 -0
  23. package/dist/types/conductor/signal-hub/translate.d.ts +4 -1
  24. package/dist/types/console/auth-status.d.ts +28 -0
  25. package/dist/types/console/components/AgentsView.d.ts +41 -0
  26. package/dist/types/console/components/BackgroundAgents.d.ts +63 -0
  27. package/dist/types/console/components/BackgroundAgents.test.d.ts +8 -0
  28. package/dist/types/console/components/Banner.d.ts +67 -33
  29. package/dist/types/console/components/TerminalConsole.d.ts +0 -5
  30. package/dist/types/console/components/welcome.d.ts +115 -0
  31. package/dist/types/console/components/welcome.test.d.ts +9 -0
  32. package/dist/types/console/contract.d.ts +16 -1
  33. package/dist/types/console/index.d.ts +3 -0
  34. package/dist/types/console/input/index.d.ts +1 -1
  35. package/dist/types/console/input/keymap.d.ts +2 -2
  36. package/dist/types/console/input/paste.d.ts +58 -6
  37. package/dist/types/console/overlays/approval-queue.d.ts +18 -1
  38. package/dist/types/console/overlays/boards.d.ts +55 -0
  39. package/dist/types/console/overlays/index.d.ts +1 -1
  40. package/dist/types/console/theme/adapter.d.ts +1 -1
  41. package/dist/types/console/theme/index.d.ts +1 -1
  42. package/dist/types/console/theme/palette.d.ts +10 -0
  43. package/dist/types/launch/login.d.ts +68 -0
  44. package/dist/types/launch/oauth.test.d.ts +20 -0
  45. package/dist/types/window-budget/summarize/condense.d.ts +6 -0
  46. package/dist/types/workflow-engine/agent-runner.d.ts +124 -0
  47. package/dist/types/workflow-engine/agent-runner.test.d.ts +8 -0
  48. package/dist/types/workflow-engine/display.d.ts +148 -0
  49. package/dist/types/workflow-engine/display.test.d.ts +1 -0
  50. package/dist/types/workflow-engine/engine.d.ts +183 -0
  51. package/dist/types/workflow-engine/engine.test.d.ts +1 -0
  52. package/dist/types/workflow-engine/index.d.ts +21 -0
  53. package/dist/types/workflow-engine/parse.d.ts +64 -0
  54. package/dist/types/workflow-engine/parse.test.d.ts +1 -0
  55. package/dist/types/workflow-engine/structured-output.d.ts +51 -0
  56. package/dist/types/workflow-engine/structured-output.test.d.ts +1 -0
  57. package/dist/types/workspace/brand.d.ts +1 -1
  58. package/package.json +2 -2
  59. package/dist/types/console/components/Emblem.d.ts +0 -49
@@ -97,11 +97,20 @@ export declare function conductorFault(kind: FaultKind, message: string, cause?:
97
97
  * - `text` — a chunk of assistant answer text streamed in.
98
98
  * - `thinking` — a chunk of reasoning/thinking text streamed in.
99
99
  * - `tool_start`— a tool invocation began (correlate by `id`).
100
+ * - `tool_update`— a running tool emitted partial progress (correlate by `id`);
101
+ * `name` is the tool name and `details` is the tool's own typed
102
+ * partial-result detail (e.g. the live `◆ Workflow` snapshot).
100
103
  * - `tool_end` — a tool invocation finished (`ok` = no error).
101
104
  * - `turn_end` — the assistant turn settled; `usage` reports token spend.
102
105
  * - `persisted` — the latest node was committed to the transcript (`entryId`).
103
- * - `compacted` — the transcript was condensed to fit the context window.
104
- * - `fault` — a typed {@link ConductorFault} occurred.
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).
105
114
  * - `queue` — the pending-input queue changed; `count` is its new depth.
106
115
  * - `idle` — the conductor has no in-flight work and is ready for input.
107
116
  */
@@ -118,6 +127,11 @@ export type SessionSignal = {
118
127
  readonly kind: "tool_start";
119
128
  readonly id: string;
120
129
  readonly name: string;
130
+ } | {
131
+ readonly kind: "tool_update";
132
+ readonly id: string;
133
+ readonly name: string;
134
+ readonly details: unknown;
121
135
  } | {
122
136
  readonly kind: "tool_end";
123
137
  readonly id: string;
@@ -130,9 +144,11 @@ export type SessionSignal = {
130
144
  readonly entryId: string;
131
145
  } | {
132
146
  readonly kind: "compacted";
147
+ readonly manual?: boolean;
133
148
  } | {
134
149
  readonly kind: "fault";
135
150
  readonly fault: ConductorFault;
151
+ readonly transient?: boolean;
136
152
  } | {
137
153
  readonly kind: "queue";
138
154
  readonly count: number;
@@ -420,6 +436,18 @@ export interface SessionConductorOptions {
420
436
  * the swap entirely (behavior-preserving default).
421
437
  */
422
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>;
423
451
  /** Working directory the session is scoped to (defaults to process cwd). */
424
452
  readonly workspace?: string;
425
453
  /**
@@ -488,6 +516,11 @@ export interface SessionConductorOptions {
488
516
  */
489
517
  readonly checkpoint?: CheckpointPort;
490
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";
491
524
  /**
492
525
  * The conductor of a single coding-agent session.
493
526
  *
@@ -529,6 +562,15 @@ export interface SessionConductor {
529
562
  pendingInputs(): readonly QueuedInput[];
530
563
  /** Discard every queued input. Emits a `{ kind: "queue" }` signal. */
531
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;
532
574
  /**
533
575
  * Remove and return the text of the most-recently queued input, or `undefined`
534
576
  * when the queue is empty. Lets a UI pop the last entry back into its prompt.
@@ -562,6 +604,20 @@ export interface SessionConductor {
562
604
  * @param id canonical id of the model to switch to
563
605
  */
564
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;
565
621
  /**
566
622
  * Replace the agent's tool deck for subsequent turns.
567
623
  *
@@ -576,11 +632,27 @@ export interface SessionConductor {
576
632
  */
577
633
  registerTools(tools: AgentTool[]): void;
578
634
  /**
579
- * Manually run the same transcript-condense path the auto-compactor uses,
580
- * emitting the existing `compacted` signal. Safe to call when idle; a no-op
581
- * when the condense hook returns the branch unchanged.
582
- */
583
- condense(): Promise<void>;
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;
584
656
  /**
585
657
  * Branch the transcript from a prior node. A new branch is opened whose parent
586
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<ApprovalChoice>;
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
- * conductor owns the mutable rule list and passes a setter here so the new rule
192
- * is visible to subsequent calls within the session.
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
- * - re-editing a file re-surfaces a previously-cleared diagnostic;
10
- * - a non-TS edit never triggers the checker;
11
- * - the injected follow-up turn (which edits nothing) does not loop.
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 {};
@@ -0,0 +1,35 @@
1
+ /**
2
+ * quota-error.ts — maps the indus model-gateway's usage-limit response into a
3
+ * friendly, user-facing fault message.
4
+ *
5
+ * When a no-local-key, logged-in user runs a turn, the framework adapter is
6
+ * pointed at `${INDUS_SERVER_URL}/gateway/<provider>` (see the conductor's
7
+ * baseUrl rewrite). If that user is over their monthly token allowance, the
8
+ * server replies — *before* forwarding upstream — with:
9
+ *
10
+ * HTTP/1.1 429 Too Many Requests
11
+ * Content-Type: application/json
12
+ *
13
+ * { "error": "quota_exceeded", "used": <number>, "limit": <number> }
14
+ *
15
+ * The provider SDK / raw-fetch adapter turns that 429 into a thrown error. By
16
+ * the time it reaches the conductor it may be wrapped (an SDK `APIError`, a
17
+ * generic `Error`, a nested `cause` chain, or a plain string), so detection must
18
+ * be tolerant and walk the whole error chain rather than trust a single shape.
19
+ *
20
+ * This module is intentionally transport-agnostic and dependency-free so it can
21
+ * be unit-tested in isolation and imported from the conductor's retry chokepoint
22
+ * without pulling in any network/runtime modules.
23
+ */
24
+ /** Detects the gateway's 429 quota body anywhere in the error chain. */
25
+ export declare function isQuotaFault(error: unknown): boolean;
26
+ /**
27
+ * Friendly user-facing text; `undefined` if `error` is not a quota fault.
28
+ *
29
+ * Includes the `<used>/<limit>` parenthetical when both numbers are parseable,
30
+ * and otherwise falls back to the same sentence without it. The `indus signin
31
+ * <provider>` hint steers the user toward supplying their own key (which takes
32
+ * the direct, un-metered local-key path), and the upgrade hint covers raising
33
+ * the server-side allowance.
34
+ */
35
+ export declare function quotaErrorMessage(error: unknown): string | undefined;
@@ -24,7 +24,10 @@
24
24
  * (the turn is now committed), one `fault` for
25
25
  * an errored *assistant* message, else `[]`
26
26
  * - `tool_execution_start` → one `tool_start` { id, name }
27
- * - `tool_execution_update` → `[]` (partial tool progress is not surfaced)
27
+ * - `tool_execution_update` → one `tool_update` { id, name, details } — the
28
+ * running tool's partial-result detail (e.g. the
29
+ * live `◆ Workflow` snapshot) re-projected so the
30
+ * console can update the transcript in place
28
31
  * - `tool_execution_end` → one `tool_end` { id, ok: !isError }
29
32
  * - `turn_end` → one `turn_end` { usage }
30
33
  * - `message_update` → delegated to the inner streaming dispatch on
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Shared "does the user have a usable credential for this provider" probe.
3
+ *
4
+ * Used anywhere the console needs to know which providers are actually
5
+ * callable — the `/model` picker (only list authenticated providers) and the
6
+ * banner's Session panel (don't display an unauthenticated model as if it were
7
+ * ready to use) both need the exact same answer, so it lives here once.
8
+ */
9
+ import type { OverlayServices } from "./contract";
10
+ /**
11
+ * Load the set of provider ids the user has USABLE credentials for, live, so a
12
+ * mid-session `/login` (either a local vault sign-in or the "Indus Server"
13
+ * server-tier device flow) is reflected without a restart.
14
+ *
15
+ * Two independent sources count: (1) any saved account in the local vault for a
16
+ * provider `listLoginProviders()` names, and (2) a valid server session token,
17
+ * which makes every gateway-eligible provider (`serverTierProviders()` —
18
+ * minimax, sarvam) usable even though the server tier never writes into the
19
+ * vault. Returns `null` until the asynchronous probe settles.
20
+ *
21
+ * `refreshKey` re-runs the probe whenever it changes by reference/value — a
22
+ * short-lived overlay (the `/model` picker) can leave it at its default since
23
+ * it remounts fresh on every open anyway, but a component mounted once for the
24
+ * whole session (the banner, the status bar) MUST pass something that changes
25
+ * right when a sign-in completes (e.g. the console's current status toast),
26
+ * otherwise this only ever reflects whatever was true at session start.
27
+ */
28
+ export declare function useAuthenticatedProviders(services: OverlayServices | undefined, refreshKey?: unknown): Set<string> | null;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * AgentsView — the drill-in sub-agent view, styled to the "IndusCode Saffron ·
3
+ * Subagent View" design.
4
+ *
5
+ * Opened from the main screen (← on an empty prompt while sub-agents are
6
+ * running), this replaces the transcript with a focused, navigable view of the
7
+ * delegated sub-agents: ↑/↓ moves the selection through `main` + each sub-agent,
8
+ * and the panel above the picker live-previews the selected sub-agent's activity
9
+ * stream — its reasoning and the tools it is using, just like the main screen —
10
+ * with a running clock and token count. Selecting `main` and pressing Enter (or
11
+ * Esc) returns to the main session.
12
+ *
13
+ * It is fed entirely by props the surface already holds: the derived
14
+ * {@link AgentRun}s plus the per-sub-agent live activity / token maps captured
15
+ * from the task tool's streamed progress. Its own {@link useInput} owns the
16
+ * keyboard while it is mounted (the surface gates its main handler off).
17
+ */
18
+ import { type InkThemeAdapter } from "indusagi/react-ink";
19
+ import type { AgentRun } from "./BackgroundAgents";
20
+ import type { ActivityLine } from "../../capability-deck/cards/task-card";
21
+ /** What the {@link AgentsView} renders. */
22
+ export interface AgentsViewProps {
23
+ /** The framework adapter that turns token roles into terminal colours. */
24
+ readonly theme: InkThemeAdapter;
25
+ /** The delegated sub-agent runs (from `deriveAgentRuns`). */
26
+ readonly runs: readonly AgentRun[];
27
+ /** Live activity streams per run id (from the task tool's progress). */
28
+ readonly activity: Readonly<Record<string, readonly ActivityLine[]>>;
29
+ /** Live token spend per run id. */
30
+ readonly tokens: Readonly<Record<string, number>>;
31
+ /** Current epoch ms, for the run clock. Defaults to `Date.now()`. */
32
+ readonly now?: number;
33
+ /** Return to the main session. */
34
+ readonly onExit: () => void;
35
+ }
36
+ /**
37
+ * Render the drill-in agents view.
38
+ *
39
+ * @param props the theme, runs, live activity/token maps, and the exit callback
40
+ */
41
+ export declare function AgentsView({ theme, runs, activity, tokens, now, onExit }: AgentsViewProps): JSX.Element;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * BackgroundAgents — the live sub-agent panel pinned below the input, styled to
3
+ * the "IndusCode Saffron · Background Agents" design.
4
+ *
5
+ * When the primary agent delegates with the `task` tool, each sub-agent run is
6
+ * surfaced here as a compact status row (a spinner/● dot, the agent profile name
7
+ * in saffron, the objective in dim, and a running clock / done / failed status)
8
+ * under a `● main` header — so a user watching several agents work sees them all
9
+ * at a glance in the main screen instead of scrolling the transcript. The panel
10
+ * shows only while at least one run is in flight and hides once they all settle
11
+ * (their final reports remain inline in the transcript).
12
+ *
13
+ * The run list is derived purely from the conversation by {@link deriveAgentRuns}
14
+ * (the `task` tool calls in the assistant messages, matched against their
15
+ * `toolResult` messages for status), so the panel needs no extra session state —
16
+ * it is a projection of `conductor.messages()`.
17
+ */
18
+ import { type InkThemeAdapter } from "indusagi/react-ink";
19
+ import type { AgentMessage } from "indusagi/agent";
20
+ /** The live status of one delegated sub-agent. */
21
+ export type AgentRunStatus = "running" | "done" | "error";
22
+ /** One delegated sub-agent run, projected from the conversation. */
23
+ export interface AgentRun {
24
+ /** The `task` tool-call id correlating the call with its result. */
25
+ readonly id: string;
26
+ /** The agent profile name (the `agent` arg), or "subagent" when unnamed. */
27
+ readonly name: string;
28
+ /** The delegated objective (the `objective` arg), shown as the description. */
29
+ readonly objective: string;
30
+ /** Whether the run is in flight, finished, or errored. */
31
+ readonly status: AgentRunStatus;
32
+ /** Epoch ms the delegating assistant message was stamped (for the run clock). */
33
+ readonly startedAt: number;
34
+ }
35
+ /**
36
+ * Derive the delegated sub-agent runs from the conversation.
37
+ *
38
+ * Walks the messages once: every `task` tool call in an assistant message
39
+ * becomes a run (named by its `agent` arg, described by its `objective`), and a
40
+ * later `toolResult` with the matching id resolves its status to done/error.
41
+ * Runs with no result yet are "running". Pure and order-stable — the same
42
+ * messages always yield the same list, oldest first.
43
+ *
44
+ * @param messages the live conversation (`conductor.messages()`)
45
+ */
46
+ export declare function deriveAgentRuns(messages: readonly AgentMessage[]): AgentRun[];
47
+ /** What the {@link BackgroundAgents} renders. */
48
+ export interface BackgroundAgentsProps {
49
+ /** The framework adapter that turns token roles into terminal colours. */
50
+ readonly theme: InkThemeAdapter;
51
+ /** The derived sub-agent runs (from {@link deriveAgentRuns}). */
52
+ readonly runs: readonly AgentRun[];
53
+ /** Live token spend per run, keyed by run id (the task tool-call id). */
54
+ readonly tokens?: Readonly<Record<string, number>>;
55
+ /** Current epoch ms, for the run clock. Defaults to `Date.now()`. */
56
+ readonly now?: number;
57
+ }
58
+ /**
59
+ * Render the background-agents panel, or nothing when no run is in flight.
60
+ *
61
+ * @param props the theme, the derived runs, and the current time
62
+ */
63
+ export declare function BackgroundAgents({ theme, runs, tokens, now }: BackgroundAgentsProps): JSX.Element | null;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * BackgroundAgents — pure derivation tests.
3
+ *
4
+ * Exercises {@link deriveAgentRuns} against hand-built conversations (no Ink):
5
+ * `task` tool calls become runs, matched `toolResult` messages resolve their
6
+ * status, non-task calls are ignored, and unnamed agents fall back to a label.
7
+ */
8
+ export {};
@@ -1,18 +1,30 @@
1
1
  /**
2
- * Banner — the masthead the console renders above the transcript.
2
+ * Banner — the "IndusCode Saffron" masthead the console renders above the
3
+ * transcript.
3
4
  *
4
- * The startup chrome the surface mounts once at the top of a session: an
5
- * original block-letter wordmark rendered in the box-drawing palette and tinted
6
- * with the accent/signal role, a version + brand line beneath it, and a compact
7
- * bordered "Session" panel of at-a-glance facts (the bound model id and the
8
- * working directory). It reads nothing but its props, holds no state, and runs
9
- * no effects — purely presentational Ink primitives themed through the framework
10
- * {@link InkThemeAdapter}.
5
+ * The startup chrome the surface mounts at the top of a session. Its loud
6
+ * (default) mode has two presentations, switched by {@link BannerProps.started}:
11
7
  *
12
- * The wordmark is the "INDUS CODE" brand masthead rendered in the ANSI-Shadow
13
- * block-figlet style from the `█ ║ ═ ╔ ╗ ╚ ╝` box-drawing family — the same
14
- * masthead the shipped console shows, so both surfaces share one identity. The
15
- * glyph rows are plain data tinted with the accent role at render time.
8
+ * - **Welcome card** (fresh session, no messages yet): a bordered card in the
9
+ * saffron brand look — a compact `● IndusCode v…` brand label, then two
10
+ * columns (left: a personalized greeting above a small block-art robot
11
+ * mascot and the bound model / working directory; right: a rotating "getting
12
+ * started" tip and the "What's new" notes). The mascot expression and the
13
+ * tip rotate per session (seeded once at mount and threaded in via
14
+ * {@link BannerProps.variantSeed}), so a new session looks a little different
15
+ * each time while staying deterministic for tests.
16
+ * - **Startup & Chat masthead** (once the user has sent a message): the
17
+ * `➜ dir git:(branch) indus` shell line, the block-letter "INDUS CODE"
18
+ * wordmark, the `indus console v…` brand line, the welcome line, and a
19
+ * bordered "Session" panel (model + cwd). The conversation itself is rendered
20
+ * below the banner by the surface's `MessageList`.
21
+ *
22
+ * It reads nothing but its props, holds no state, and runs no effects — purely
23
+ * presentational Ink primitives themed through the framework
24
+ * {@link InkThemeAdapter}. The {@link BannerProps.quiet} or
25
+ * {@link BannerProps.compact} flags collapse all of that to a single compact
26
+ * header line, still carrying the welcome line, the notices, and a condensed
27
+ * changelog so nothing important is silently dropped.
16
28
  */
17
29
  import { type InkThemeAdapter } from "indusagi/react-ink";
18
30
  import type { StartupChangelog, StartupMap, StartupNotice } from "../startup";
@@ -26,19 +38,27 @@ export interface BannerProps {
26
38
  readonly workspace: string;
27
39
  /** The product version shown on the brand line (e.g. the package VERSION). */
28
40
  readonly version: string;
41
+ /**
42
+ * Whether the conversation has begun (the user has sent at least one message).
43
+ * When `false` the loud banner is the fresh-session welcome card; when `true`
44
+ * it becomes the Startup & Chat masthead (wordmark + Session panel) that sits
45
+ * above the transcript. Has no effect in {@link quiet}/{@link compact} modes.
46
+ */
47
+ readonly started?: boolean;
48
+ /** The active VCS branch (for the masthead shell line), or `null`/absent outside a repo. */
49
+ readonly branch?: string | null;
29
50
  /** Whether to render the extra diagnostics line. */
30
51
  readonly verbose?: boolean;
31
52
  /**
32
- * When set, the big wordmark + Startup Map are suppressed in favour of a
33
- * single compact header line. Wired from the verbose / quiet-startup flag.
53
+ * When set, the welcome card is suppressed in favour of a single compact
54
+ * header line. Wired from the verbose / quiet-startup flag.
34
55
  */
35
56
  readonly quiet?: boolean;
36
57
  /**
37
58
  * When set, the masthead auto-condenses to a single emblem + brand + model
38
- * line (the repeat-launch presentation): the user has already seen this
39
- * version's full masthead, so the big wordmark is skipped. Distinct from
40
- * {@link quiet}, which is the manual suppression toggle; either collapses the
41
- * banner, but `compact` keeps the small emblem and the welcome line.
59
+ * line (the repeat-launch presentation). Distinct from {@link quiet}, which is
60
+ * the manual suppression toggle; either collapses the banner, but `compact`
61
+ * keeps the small emblem and the welcome line.
42
62
  */
43
63
  readonly compact?: boolean;
44
64
  /**
@@ -47,15 +67,30 @@ export interface BannerProps {
47
67
  */
48
68
  readonly name?: string;
49
69
  /**
50
- * Opt-in static colour-sweep flourish: when set, the wordmark and emblem fill
51
- * are tinted along a frozen primary→secondary gradient instead of the flat
52
- * accent. The caller is responsible for suppressing it under reduced-motion /
53
- * non-TTY; this prop is simply the resolved on/off decision.
70
+ * Opt-in static colour-sweep flourish (legacy; accepted for call-site
71
+ * compatibility). The saffron welcome card paints from the theme roles and
72
+ * does not consume this flag.
54
73
  */
55
74
  readonly sweep?: boolean;
56
- /** The gathered session resources rendered as the Startup Map panel. */
75
+ /**
76
+ * The per-session variation seed. Selects which mascot expression + "getting
77
+ * started" tip the card shows; the same seed always yields the same pair, so a
78
+ * test pins an exact variant. Absent → the first (seed 0) variant.
79
+ */
80
+ readonly variantSeed?: number;
81
+ /**
82
+ * Optional override for the "What's new" notes (e.g. sourced from a live
83
+ * changelog). Absent → the shipped {@link WHATS_NEW} copy.
84
+ */
85
+ readonly whatsNew?: readonly string[];
86
+ /**
87
+ * The gathered session resources (context docs, skills, prompts). Reserved:
88
+ * the saffron welcome card shows quick-action chips in place of the old
89
+ * "Startup Map" panel, so this is accepted for call-site compatibility (and to
90
+ * keep the gathering machinery wired) but not rendered in the card.
91
+ */
57
92
  readonly startup?: StartupMap;
58
- /** Out-of-band lines drawn above the wordmark (errors, warnings, info). */
93
+ /** Out-of-band lines drawn above the card (errors, warnings, info). */
59
94
  readonly notices?: readonly StartupNotice[];
60
95
  /** The changelog survey rendered as a "What is new" block on a version bump. */
61
96
  readonly changelog?: StartupChangelog;
@@ -63,14 +98,13 @@ export interface BannerProps {
63
98
  /**
64
99
  * Render the console masthead.
65
100
  *
66
- * In the default (loud) mode this is the two-tone emblem beside the block-letter
67
- * wordmark, the brand / version line, the personalized welcome line, the
68
- * optional notices region, the bordered Startup Map, and the changelog block.
69
- * The {@link BannerProps.quiet} or {@link BannerProps.compact} flags collapse
70
- * all of that to a single compact header line (emblem glyph + brand + version +
71
- * model), still carrying the welcome line, the notices, and a condensed
72
- * changelog so nothing important is silently dropped.
101
+ * In the default (loud) mode this is the saffron welcome card: a `● IndusCode
102
+ * v…` brand label and a two-column body (greeting + mascot + model/cwd on the
103
+ * left, a rotating tip + the "What's new" notes on the right). The
104
+ * {@link BannerProps.quiet} or {@link BannerProps.compact} flags
105
+ * collapse all of that to a single compact header line, still carrying the
106
+ * welcome line, the notices, and a condensed changelog.
73
107
  *
74
- * @param props the wordmark context, version, session facts, and startup chrome
108
+ * @param props the brand/version context, session facts, and startup chrome
75
109
  */
76
- export declare function Banner({ theme, modelId, workspace, version, verbose, quiet, compact, name, sweep, startup, notices, changelog, }: BannerProps): JSX.Element;
110
+ export declare function Banner({ theme, modelId, workspace, version, started, branch, quiet, compact, name, variantSeed, whatsNew, notices, changelog, }: BannerProps): JSX.Element;
@@ -24,9 +24,4 @@
24
24
  * `indusagi/react-ink`.
25
25
  */
26
26
  import type { ConsoleProps } from "../contract";
27
- /**
28
- * The interactive console surface.
29
- *
30
- * @param props the conductor to drive, the resolved theme, and the slash registry
31
- */
32
27
  export declare function TerminalConsole(props: ConsoleProps): JSX.Element;