indusagi-coding-agent 0.2.2 → 0.2.3

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 (41) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/entry.js +10975 -9147
  3. package/dist/guardrails.js +876 -31
  4. package/dist/index.js +12738 -10903
  5. package/dist/types/boot/runners/delegate-runner.d.ts +6 -0
  6. package/dist/types/boot/runners/session.d.ts +2 -17
  7. package/dist/types/capability-deck/cards/index.d.ts +1 -0
  8. package/dist/types/capability-deck/cards/task-card.d.ts +26 -2
  9. package/dist/types/capability-deck/cards/workflow-card.d.ts +55 -0
  10. package/dist/types/capability-deck/cards/workflow-card.test.d.ts +12 -0
  11. package/dist/types/capability-deck/index.d.ts +1 -1
  12. package/dist/types/conductor/contract.d.ts +8 -0
  13. package/dist/types/conductor/signal-hub/translate.d.ts +4 -1
  14. package/dist/types/console/components/AgentsView.d.ts +41 -0
  15. package/dist/types/console/components/BackgroundAgents.d.ts +63 -0
  16. package/dist/types/console/components/BackgroundAgents.test.d.ts +8 -0
  17. package/dist/types/console/components/Banner.d.ts +67 -33
  18. package/dist/types/console/components/welcome.d.ts +115 -0
  19. package/dist/types/console/components/welcome.test.d.ts +9 -0
  20. package/dist/types/console/contract.d.ts +3 -1
  21. package/dist/types/console/index.d.ts +3 -0
  22. package/dist/types/console/input/keymap.d.ts +1 -1
  23. package/dist/types/console/input/paste.d.ts +5 -5
  24. package/dist/types/console/overlays/boards.d.ts +55 -0
  25. package/dist/types/console/theme/adapter.d.ts +1 -1
  26. package/dist/types/console/theme/index.d.ts +1 -1
  27. package/dist/types/console/theme/palette.d.ts +10 -0
  28. package/dist/types/workflow-engine/agent-runner.d.ts +105 -0
  29. package/dist/types/workflow-engine/agent-runner.test.d.ts +8 -0
  30. package/dist/types/workflow-engine/display.d.ts +148 -0
  31. package/dist/types/workflow-engine/display.test.d.ts +1 -0
  32. package/dist/types/workflow-engine/engine.d.ts +183 -0
  33. package/dist/types/workflow-engine/engine.test.d.ts +1 -0
  34. package/dist/types/workflow-engine/index.d.ts +21 -0
  35. package/dist/types/workflow-engine/parse.d.ts +64 -0
  36. package/dist/types/workflow-engine/parse.test.d.ts +1 -0
  37. package/dist/types/workflow-engine/structured-output.d.ts +51 -0
  38. package/dist/types/workflow-engine/structured-output.test.d.ts +1 -0
  39. package/dist/types/workspace/brand.d.ts +1 -1
  40. package/package.json +2 -2
  41. package/dist/types/console/components/Emblem.d.ts +0 -49
@@ -43,6 +43,12 @@ export interface DelegateSubAgent {
43
43
  messages: readonly AgentMessage[];
44
44
  error?: string;
45
45
  };
46
+ /**
47
+ * Optional event subscription (the real framework `Agent` provides it). The
48
+ * runner uses it to recompute live token spend as the sub-agent works; a test
49
+ * fake may omit it, in which case no progress is reported.
50
+ */
51
+ subscribe?(listener: () => void): () => void;
46
52
  }
47
53
  /** Configuration for {@link createDelegateRunner}. */
48
54
  export interface DelegateRunnerOptions {
@@ -11,6 +11,7 @@
11
11
  import { type AgentTool, type SessionConductor } from "../../conductor";
12
12
  import { type MemoryStore } from "../../capability-deck";
13
13
  import { type DelegateRunner } from "../../capability-deck/cards/task-card";
14
+ import type { WorkflowAgentRunner } from "../../workflow-engine";
14
15
  import { type CheckpointStore } from "./checkpoint";
15
16
  import { type ContextDoc, type SkillCard } from "../../briefing";
16
17
  import type { BootContext, Invocation } from "../contract";
@@ -33,23 +34,7 @@ export declare function resolveModelId(ctx: BootContext): string;
33
34
  * bad/unreadable skills dir never sinks the session.
34
35
  */
35
36
  export declare function gatherModelSkills(cwd: string): SkillCard[];
36
- /**
37
- * Select the tool deck for the run, honouring `--no-tools` (empty) and `--tools`
38
- * (allow-list). Tool ids are matched case-insensitively with `_`/`-` stripped, so
39
- * a `--tools web_fetch,todo_read` request lines up with the deck's `webfetch` /
40
- * `todoread` ids.
41
- */
42
- /**
43
- * @param runner an optional live sub-agent {@link DelegateRunner}; when present it
44
- * is injected under {@link DELEGATE_HANDLE_KEY} so the `task` card delegates for
45
- * real instead of returning its `STUB_NOTE`. Omitted by callers that only need
46
- * a deck (the card then degrades gracefully).
47
- * @param checkpoint an optional per-session {@link CheckpointStore}; when present
48
- * it is injected under {@link CHECKPOINT_HANDLE_KEY} so the framework's
49
- * write/edit tools snapshot a file's pre-mutation content (rewind, #24). Omitted
50
- * callers get no checkpointing (the tools no-op the snapshot).
51
- */
52
- export declare function selectTools(cwd: string, inv: Invocation, runner?: DelegateRunner, memoryStore?: MemoryStore, checkpoint?: CheckpointStore): AgentTool[];
37
+ export declare function selectTools(cwd: string, inv: Invocation, runner?: DelegateRunner, memoryStore?: MemoryStore, checkpoint?: CheckpointStore, workflowRunner?: WorkflowAgentRunner): AgentTool[];
53
38
  /**
54
39
  * Compose the run's system prompt: `--system` replaces the built-in briefing,
55
40
  * `--append-system` adds a trailing block, and both compose (override then
@@ -21,6 +21,7 @@ import type { CapabilityCard } from "../contract";
21
21
  export { todoCard, buildTodoCapability, TodoLedger, type TodoItem, type TodoState, type TodoWeight, type TodoParamsType, type TodoDetails, } from "./todo-card";
22
22
  export { daemonCard, buildDaemonCapability, DaemonTable, type DaemonState, type DaemonParamsType, type DaemonDetails, } from "./bg-process-card";
23
23
  export { taskCard, buildTaskCapability, DELEGATE_HANDLE_KEY, type DelegateRunner, type DelegateRequest, type DelegateResult, type TaskParamsType, type TaskDetails, } from "./task-card";
24
+ export { workflowCard, buildWorkflowCapability, WORKFLOW_HANDLE_KEY, type WorkflowParamsType, type WorkflowDetails, } from "./workflow-card";
24
25
  export { saasCard, buildSaasCapability, SAAS_GATEWAY_KEY, type SaasGatewayPort, type RemoteToolSummary, type RemoteExecution, type SaasParamsType, type SaasDetails, } from "./saas-card";
25
26
  export { memoryCard, buildMemoryCapability, InMemoryStore, MEMORY_HANDLE_KEY, type MemoryStore, type MemoryParamsType, type MemoryDetails, } from "./memory-card";
26
27
  export { enterPlanModeCard, exitPlanModeCard, buildEnterPlanModeCapability, buildExitPlanModeCapability, PLAN_HANDLE_KEY, type PlanController, type EnterPlanParamsType, type EnterPlanDetails, type ExitPlanParamsType, type ExitPlanDetails, } from "./plan-tools";
@@ -37,6 +37,20 @@ export interface DelegateResult {
37
37
  /** The sub-agent's final report, surfaced to the parent agent verbatim. */
38
38
  readonly report: string;
39
39
  }
40
+ /** One line of a sub-agent's live activity stream (for the drill-in view). */
41
+ export interface ActivityLine {
42
+ /** What kind of step this is, used to colour it. */
43
+ readonly tone: "reason" | "tool" | "result";
44
+ /** The display text (already shortened to a single line). */
45
+ readonly text: string;
46
+ }
47
+ /** Live progress a running sub-agent streams back while it works. */
48
+ export interface DelegateProgress {
49
+ /** Cumulative tokens the sub-agent has spent so far (input + output). */
50
+ readonly tokens: number;
51
+ /** The sub-agent's recent activity (reasoning + tool steps), newest last. */
52
+ readonly activity: readonly ActivityLine[];
53
+ }
40
54
  /**
41
55
  * The contract a host's sub-agent runner must satisfy to be wired in.
42
56
  *
@@ -47,8 +61,14 @@ export interface DelegateResult {
47
61
  export interface DelegateRunner {
48
62
  /** Optional roster of named agent profiles, surfaced in the tool description. */
49
63
  listAgents?(): readonly string[];
50
- /** Run one delegated objective and resolve with the sub-agent's report. */
51
- run(request: DelegateRequest, signal?: AbortSignal): Promise<DelegateResult>;
64
+ /**
65
+ * Run one delegated objective and resolve with the sub-agent's report.
66
+ *
67
+ * When an `onProgress` callback is supplied the runner streams live metrics
68
+ * (token spend) as the sub-agent works, so the host can surface them (the
69
+ * background-agents panel). Optional — a runner may ignore it.
70
+ */
71
+ run(request: DelegateRequest, signal?: AbortSignal, onProgress?: (progress: DelegateProgress) => void): Promise<DelegateResult>;
52
72
  }
53
73
  declare const TaskParams: import("@sinclair/typebox").TObject<{
54
74
  objective: import("@sinclair/typebox").TString;
@@ -65,6 +85,10 @@ export interface TaskDetails {
65
85
  readonly ok: boolean;
66
86
  /** The agent profile that ran, if one was named. */
67
87
  readonly agent?: string;
88
+ /** Cumulative tokens the sub-agent spent — streamed live on progress updates. */
89
+ readonly tokens?: number;
90
+ /** The sub-agent's recent activity — streamed live for the drill-in view. */
91
+ readonly activity?: readonly ActivityLine[];
68
92
  }
69
93
  /**
70
94
  * Build the task/delegate capability.
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Workflow capability — orchestrate a deterministic fan-out of subagents.
3
+ *
4
+ * App-novel wiring, sibling to (and UNRELATED to) the {@link "./task-card"}
5
+ * delegate card. The `workflow` tool runs a deterministic JavaScript workflow
6
+ * script that calls `agent()`, `parallel()`, and `pipeline()` to fan work out
7
+ * across many fresh subagents and fan their results back in, all under a live
8
+ * `◆ Workflow:` progress tree streamed into the tool result.
9
+ *
10
+ * The actual subagent runner is NOT owned by this card — it is a framework /
11
+ * boot concern injected through {@link DeckContext.framework} under the
12
+ * {@link WORKFLOW_HANDLE_KEY} key. When that handle is present the capability
13
+ * really runs the workflow (via the pure {@link runWorkflow} engine); when it is
14
+ * absent (tests, headless tooling, a host that has not wired the runner) the
15
+ * capability degrades gracefully to a clearly-typed {@link STUB_NOTE} result
16
+ * rather than throwing, exactly like the task card's `readDelegateRunner` path.
17
+ *
18
+ * Recursion guard: this card is registered all-profile-only in
19
+ * {@link "../cards/index"} (`APP_NOVEL_CARDS`), so the `'authoring'` deck a
20
+ * workflow subagent runs against EXCLUDES it — a subagent cannot call `workflow`.
21
+ *
22
+ * Live UI: each engine lifecycle callback mutates a {@link WorkflowSnapshot} and
23
+ * calls the framework `onUpdate` with the snapshot rendered through
24
+ * {@link renderWorkflowText} (the v1 string renderer). Updates are coalesced to
25
+ * status transitions (phase / agent start / agent end), not every log line, to
26
+ * avoid render thrash. The conductor re-projects the framework
27
+ * `tool_execution_update` event to a `tool_update` product signal (stage 3).
28
+ */
29
+ import { type Static } from "@sinclair/typebox";
30
+ import type { Capability, CapabilityCard, DeckContext } from "../contract";
31
+ import { type WorkflowSnapshot } from "../../workflow-engine";
32
+ /** Key under which a host wires a live workflow agent runner into the context. */
33
+ export declare const WORKFLOW_HANDLE_KEY: "workflow";
34
+ declare const WorkflowParams: import("@sinclair/typebox").TObject<{
35
+ script: import("@sinclair/typebox").TString;
36
+ args: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TAny>;
37
+ }>;
38
+ /** Statically-inferred parameter type the capability's `execute` receives. */
39
+ export type WorkflowParamsType = Static<typeof WorkflowParams>;
40
+ /** Structured detail returned alongside the model-facing content (the snapshot). */
41
+ export type WorkflowDetails = WorkflowSnapshot;
42
+ /**
43
+ * Build the workflow capability.
44
+ *
45
+ * If a {@link WorkflowAgentRunner} is present on the context it is bound and the
46
+ * tool truly orchestrates; otherwise the tool builds anyway and returns a typed,
47
+ * non-throwing stub so the deck stays assemblable in every environment.
48
+ *
49
+ * @param ctx the deck context; an optional runner is read from
50
+ * `ctx.framework[WORKFLOW_HANDLE_KEY]`.
51
+ */
52
+ export declare function buildWorkflowCapability(ctx: DeckContext): Capability<typeof WorkflowParams, WorkflowDetails>;
53
+ /** Catalog row for the workflow capability. */
54
+ export declare const workflowCard: CapabilityCard;
55
+ export {};
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Workflow card — focused unit tests.
3
+ *
4
+ * The card is a pure capability builder over the pure {@link runWorkflow} engine
5
+ * (an injected {@link WorkflowAgentRunner} does the spawning). These tests cover:
6
+ * - the STUB_NOTE degrade when no runner handle is wired (no throw),
7
+ * - a real run with an in-memory fake runner: lifecycle → snapshot,
8
+ * - abort flips running agents to skipped,
9
+ * - and the deck-assembly guarantee: workflow appears in the `all` profile but
10
+ * NOT in the `authoring` subagent profile (the recursion guard).
11
+ */
12
+ 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, 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";
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, workflowCard, buildWorkflowCapability, WORKFLOW_HANDLE_KEY, type WorkflowParamsType, type WorkflowDetails, 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
@@ -97,6 +97,9 @@ 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`).
@@ -118,6 +121,11 @@ export type SessionSignal = {
118
121
  readonly kind: "tool_start";
119
122
  readonly id: string;
120
123
  readonly name: string;
124
+ } | {
125
+ readonly kind: "tool_update";
126
+ readonly id: string;
127
+ readonly name: string;
128
+ readonly details: unknown;
121
129
  } | {
122
130
  readonly kind: "tool_end";
123
131
  readonly id: string;
@@ -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,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;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Welcome-card content & per-session variation — the pure data behind the
3
+ * "IndusCode Saffron" startup card (the boxed masthead the {@link Banner} draws
4
+ * in its loud mode).
5
+ *
6
+ * This module holds *no* React or Ink: it is the render-agnostic source of the
7
+ * card's rotating mascot, the rotating "getting started" tip, the static
8
+ * "what's new" notes, the slash chips, and the small block-art robot mascot
9
+ * geometry. The banner reads these and paints them through the theme adapter;
10
+ * the geometry is returned as tone-tagged {@link MascotSegment}s so the banner
11
+ * maps a tone → a colour role without re-deriving the layout.
12
+ *
13
+ * Per-session freshness ("every new session the welcome looks a little
14
+ * different") is a *pure* selection: {@link pickWelcomeVariant} maps an integer
15
+ * seed onto one mascot + one tip deterministically, so a test pins an exact
16
+ * variant while the live surface seeds it once at mount from
17
+ * {@link randomWelcomeSeed}. Nothing here reads a clock or mutates state — same
18
+ * seed in, same variant out.
19
+ */
20
+ /**
21
+ * One robot-mascot face: the two eye glyphs and the three-cell mouth.
22
+ *
23
+ * The face is drawn into a fixed nine-cell-wide rounded head (see
24
+ * {@link mascotRows}); only the eyes and mouth change between variants, so a
25
+ * face is just those glyphs plus a human-readable key for tests/logs. Each eye
26
+ * is exactly one display cell and the mouth exactly three so every face packs
27
+ * into the same head outline without shifting the columns.
28
+ */
29
+ export interface MascotFace {
30
+ /** Stable identifier for the expression (for tests and selection logs). */
31
+ readonly key: string;
32
+ /** The left/right eye glyphs (one display cell each). */
33
+ readonly eyes: readonly [string, string];
34
+ /** The three-cell mouth glyph row. */
35
+ readonly mouth: string;
36
+ }
37
+ /**
38
+ * The rotating mascot expressions, in selection order.
39
+ *
40
+ * Each is the same rounded saffron head with dark eyes and a teal mouth — the
41
+ * expression is the only thing that changes session to session.
42
+ */
43
+ export declare const MASCOTS: readonly MascotFace[];
44
+ /** A drawn cell's colour intent, mapped to a theme role by the banner. */
45
+ export type MascotTone = "head" | "eye" | "mouth" | "feet";
46
+ /** One painted span of a mascot row: literal text plus its colour intent. */
47
+ export interface MascotSegment {
48
+ /** The literal glyphs to draw. */
49
+ readonly text: string;
50
+ /** Which colour role the banner paints this span in. */
51
+ readonly tone: MascotTone;
52
+ }
53
+ /**
54
+ * Lay a {@link MascotFace} out into its drawable rows.
55
+ *
56
+ * Returns one array of {@link MascotSegment}s per terminal row of the mascot — a
57
+ * five-row rounded head (top, eyes, mouth, base, feet). The head outline is the
58
+ * `head` tone, the eyes the `eye` tone, the mouth the `mouth` tone, and the feet
59
+ * the `feet` tone; the banner resolves each tone to a concrete colour. Every row
60
+ * is the same nine-cell width (the feet row is centred under it), so the column
61
+ * stays rectangular whichever expression is chosen.
62
+ *
63
+ * @param face the expression to lay out
64
+ */
65
+ export declare function mascotRows(face: MascotFace): readonly (readonly MascotSegment[])[];
66
+ /**
67
+ * The rotating "Tips for getting started" lines.
68
+ *
69
+ * One is shown per session (chosen by {@link pickWelcomeVariant}); the pool is
70
+ * ordinary onboarding guidance so a returning user sees a different nudge each
71
+ * launch rather than the same line forever.
72
+ */
73
+ export declare const TIPS: readonly string[];
74
+ /**
75
+ * The static "What's new" notes shown in the card's right column.
76
+ *
77
+ * These are stable per release (the banner can override them from a live
78
+ * changelog); they describe the most recent user-facing changes.
79
+ */
80
+ export declare const WHATS_NEW: readonly string[];
81
+ /** The chosen per-session presentation: one mascot face and one tip line. */
82
+ export interface WelcomeVariant {
83
+ /** The mascot expression to draw this session. */
84
+ readonly mascot: MascotFace;
85
+ /** The "getting started" tip to show this session. */
86
+ readonly tip: string;
87
+ }
88
+ /**
89
+ * Pick the welcome card's mascot + tip for a session seed.
90
+ *
91
+ * Pure and total: the same seed always yields the same variant, and any integer
92
+ * (including a negative one) lands on a valid mascot and tip. The mascot and the
93
+ * tip are advanced by different strides of the seed so they do not move in
94
+ * lock-step — two nearby seeds vary both independently.
95
+ *
96
+ * @param seed the per-session seed (e.g. from {@link randomWelcomeSeed})
97
+ */
98
+ export declare function pickWelcomeVariant(seed: number): WelcomeVariant;
99
+ /**
100
+ * The PINNED welcome-card variant seed. The card no longer rotates per session
101
+ * (one stable brand look): seeded with this, {@link pickWelcomeVariant} always
102
+ * resolves to the clean "focus" mascot (`MASCOTS[0]` — `● ●` eyes, flat `───`
103
+ * mouth) + the Shift+Tab tip (`TIPS[2]`). (`12 % MASCOTS.length === 0` → focus;
104
+ * `Math.trunc(12 / MASCOTS.length) % TIPS.length === 2` → the Shift+Tab tip.) Swap
105
+ * the mount back to {@link randomWelcomeSeed} to restore per-session rotation.
106
+ */
107
+ export declare const PINNED_WELCOME_SEED = 12;
108
+ /**
109
+ * Mint a fresh seed for the current session.
110
+ *
111
+ * Impure by design (it reads the RNG), so the live surface calls it *once* at
112
+ * mount and threads the result into the otherwise-pure banner. Tests never call
113
+ * this — they pass a fixed seed to {@link pickWelcomeVariant} instead.
114
+ */
115
+ export declare function randomWelcomeSeed(): number;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Welcome-card content & variation — pure behavioural tests.
3
+ *
4
+ * These exercise the render-agnostic data behind the saffron welcome card
5
+ * without mounting Ink: the per-session variant selection (deterministic,
6
+ * total, independent strides) and the mascot geometry (rectangular, tone-tagged,
7
+ * expression-driven).
8
+ */
9
+ export {};
@@ -60,13 +60,15 @@ export type { SessionConductor, SessionSignal, ConductorState };
60
60
  *
61
61
  * Named for the time-of-day they evoke rather than a bare light/dark axis, with
62
62
  * a daltonized (color-blind-friendly) variant of each:
63
+ * - `saffron` — the IndusCode brand scheme: a warm saffron-on-cream dark
64
+ * terminal look with a cool teal accent. The shipped default.
63
65
  * - `midnight` — a low-luminance scheme for dark terminals.
64
66
  * - `daylight` — a high-luminance scheme for light terminals.
65
67
  * - `midnight-cb` — the dark scheme re-derived so success vs failure separates
66
68
  * off the red-green axis (success → blue), deuteran/protan-safe.
67
69
  * - `daylight-cb` — the light scheme's color-blind-safe counterpart.
68
70
  */
69
- export type ThemeScheme = "midnight" | "daylight" | "midnight-cb" | "daylight-cb";
71
+ export type ThemeScheme = "saffron" | "midnight" | "daylight" | "midnight-cb" | "daylight-cb";
70
72
  /** The default scheme applied before any user preference is loaded. */
71
73
  export declare const DEFAULT_SCHEME: ThemeScheme;
72
74
  /**
@@ -26,6 +26,9 @@ export { TerminalConsole } from "./components/TerminalConsole";
26
26
  export { Composer, type ComposerProps } from "./components/Composer";
27
27
  export { StatusBar, type StatusBarProps } from "./components/StatusBar";
28
28
  export { Banner, type BannerProps } from "./components/Banner";
29
+ export { BackgroundAgents, deriveAgentRuns, type BackgroundAgentsProps, type AgentRun, type AgentRunStatus, } from "./components/BackgroundAgents";
30
+ export { AgentsView, type AgentsViewProps } from "./components/AgentsView";
31
+ export { MASCOTS, TIPS, WHATS_NEW, mascotRows, pickWelcomeVariant, randomWelcomeSeed, type MascotFace, type MascotTone, type MascotSegment, type WelcomeVariant, } from "./components/welcome";
29
32
  export { gatherStartup, gatherChangelog } from "./startup";
30
33
  export type { StartupMap, StartupSection, StartupChangelog, StartupChangelogMode, StartupNotice, StartupNoticeKind, StartupInputs, } from "./startup";
31
34
  export { mountConsole, type MountConsoleOptions, type MountResult, } from "./mount";