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
@@ -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
  /**
@@ -357,6 +359,12 @@ export type ConsoleEvent = {
357
359
  } | {
358
360
  readonly type: "buffer:insert";
359
361
  readonly text: string;
362
+ }
363
+ /** Swap the first occurrence of `find` for `replaceWith` (paste-marker growth). */
364
+ | {
365
+ readonly type: "buffer:replaceSpan";
366
+ readonly find: string;
367
+ readonly replaceWith: string;
360
368
  } | {
361
369
  readonly type: "buffer:erasePrev";
362
370
  } | {
@@ -546,6 +554,13 @@ export interface OverlayServices {
546
554
  readonly openLoginUrl: (url: string) => Promise<boolean>;
547
555
  /** The credential store sign-in / sign-out overlays read and write. */
548
556
  readonly vault: AuthVault;
557
+ /**
558
+ * Recompute the server-tier gateway routing map from current vault/token
559
+ * state and push it onto the live conductor, so a sign-in that happens
560
+ * mid-session (the "Indus Server" entry in `/login`) takes effect on the
561
+ * NEXT turn without needing a session restart.
562
+ */
563
+ readonly refreshGatewayRouting: () => Promise<void>;
549
564
  }
550
565
  /**
551
566
  * What the root `TerminalConsole` surface receives at mount.
@@ -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";
@@ -18,7 +18,7 @@
18
18
  export type { KeyChord, ConsoleVerb, ConsoleIntent, ChordOutcome, ChordLatch, ChordStep, } from "./keymap";
19
19
  export { NO_INTENT, readKey, NO_CHORD, advanceChord } from "./keymap";
20
20
  export type { PasteThresholds, PasteVault, PasteCapture, } from "./paste";
21
- export { PASTE_THRESHOLDS, PASTE_BURST_GAP_MS, EMPTY_PASTE_VAULT, pasteMarker, classifyPaste, coalescePaste, expandMarkers, hasMarkers, } from "./paste";
21
+ export { PASTE_THRESHOLDS, PASTE_BURST_GAP_MS, PASTE_CONTINUATION_MS, PASTE_MARKER_LINE_CAP, EMPTY_PASTE_VAULT, pasteMarker, classifyPaste, coalescePaste, appendToPaste, pasteLines, expandMarkers, hasMarkers, } from "./paste";
22
22
  export type { CompletionKind, Suggestion, TokenSpan, CompletionResult, DirEntry, DirReader, } from "./complete";
23
23
  export { completeAt, applySuggestion } from "./complete";
24
24
  export { createDirReader } from "./dir-reader";
@@ -82,7 +82,7 @@ export interface KeyChord {
82
82
  * - `flow:cycleModel` — rotate to the next model.
83
83
  *
84
84
  * Model / reasoning
85
- * - `model:cycleThinking` — advance the reasoning-effort ladder one step.
85
+ * - `model:cycleThinking` — advance the reasoning-effort ladder one step (via /thinking command).
86
86
  *
87
87
  * Permission mode
88
88
  * - `mode:cyclePermission` — cycle through ALL permission modes (Shift+Tab),
@@ -115,7 +115,7 @@ export interface KeyChord {
115
115
  * Inert
116
116
  * - `none` — the keystroke carries no console meaning.
117
117
  */
118
- export type ConsoleVerb = "text:type" | "text:newline" | "edit:erasePrev" | "edit:eraseNext" | "edit:clearLine" | "nav:left" | "nav:right" | "nav:home" | "nav:end" | "nav:up" | "nav:down" | "flow:submit" | "flow:dismiss" | "flow:accept" | "flow:interrupt" | "flow:suspend" | "flow:cycleModel" | "model:cycleThinking" | "mode:cyclePermission" | "mode:togglePlan" | "queue:dequeue" | "input:pasteImage" | "input:externalEditor" | "overlay:open" | "view:expandTools" | "view:toggleReasoning" | "none";
118
+ export type ConsoleVerb = "text:type" | "text:newline" | "edit:erasePrev" | "edit:eraseNext" | "edit:clearLine" | "nav:left" | "nav:right" | "nav:home" | "nav:end" | "nav:up" | "nav:down" | "flow:submit" | "flow:dismiss" | "flow:accept" | "flow:interrupt" | "flow:suspend" | "flow:cycleModel" | "model:cycleThinking" | "mode:cyclePermission" | "mode:togglePlan" | "queue:dequeue" | "queue:steer" | "input:pasteImage" | "input:externalEditor" | "overlay:open" | "view:expandTools" | "view:toggleReasoning" | "none";
119
119
  /**
120
120
  * A classified keystroke: a {@link ConsoleVerb} plus the small payload a verb
121
121
  * may carry — the literal text for a `text:type` insert, or the target overlay
@@ -24,8 +24,8 @@
24
24
  * the whole paste pipeline is unit-tested in isolation. The component owns only
25
25
  * the live vault value and the burst-coalescing timer; the decisions are here.
26
26
  *
27
- * The marker format is the console's own: `‹clip N · K lines›`, where `N` is the
28
- * 1-based mint order and `K` is the hidden line count. It is chosen to be
27
+ * The marker format is the console's own: `[Pasted text #N +K lines]`, where `N` is the
28
+ * 1-based mint order and `K` is the hidden line count. Matches induscode style:
29
29
  * visually distinct in the composer, unambiguous to match, and trivial to strip.
30
30
  */
31
31
  /**
@@ -41,7 +41,14 @@ export interface PasteThresholds {
41
41
  /** Minimum character count to treat a chunk as a large paste. */
42
42
  readonly chars: number;
43
43
  }
44
- /** The default cut-offs above which a paste is coalesced into a marker. */
44
+ /**
45
+ * The default cut-offs above which a paste is coalesced into a marker.
46
+ *
47
+ * Matched to the reference agent: a paste of MORE than 10 lines (i.e. ≥10
48
+ * newlines) or more than 1000 characters hides behind a marker; anything
49
+ * smaller — including the common 8-line snippet — splices inline and renders
50
+ * as visible multi-line text in the composer.
51
+ */
45
52
  export declare const PASTE_THRESHOLDS: PasteThresholds;
46
53
  /**
47
54
  * The maximum gap, in milliseconds, between per-character events still treated
@@ -53,6 +60,30 @@ export declare const PASTE_THRESHOLDS: PasteThresholds;
53
60
  * it. Exposed as data so the coalescing timer and any test share one value.
54
61
  */
55
62
  export declare const PASTE_BURST_GAP_MS = 24;
63
+ /**
64
+ * How long, after a coalesced flush, a paste stays "active" — a further
65
+ * multi-character chunk landing within this window APPENDS to the active
66
+ * marker instead of minting a new one.
67
+ *
68
+ * Terminals deliver a big paste as a series of ~1KB stdin reads whose gaps can
69
+ * exceed any reasonable per-chunk debounce, so timing alone shreds one paste
70
+ * into many markers (`[Pasted #11][Pasted #12]…`, issue #18's second form).
71
+ * Sized GENEROUSLY: a very long paste (the spec case is 1000 lines) must
72
+ * survive mid-stream read hiccups as ONE marker, and the failure mode of a
73
+ * too-long window is mild — two lightning-fast deliberate pastes merge into
74
+ * one marker whose expansion is still the correct concatenated text — while a
75
+ * too-short window re-creates the shredding this exists to fix. Two human
76
+ * paste chords are >300ms apart in practice; single characters (real typing)
77
+ * never extend the window.
78
+ */
79
+ export declare const PASTE_CONTINUATION_MS = 250;
80
+ /**
81
+ * Ceiling on the line count a single marker absorbs via continuation. One
82
+ * user paste stays ONE marker up to this many lines; a paste that keeps
83
+ * streaming past it rolls over into a fresh marker ("one more paste") so a
84
+ * runaway multi-megabyte paste can't grow a single unbounded vault body.
85
+ */
86
+ export declare const PASTE_MARKER_LINE_CAP = 1000;
56
87
  /** The console's own placeholder format for a hidden paste body. */
57
88
  export declare function pasteMarker(ordinal: number, lineCount: number): string;
58
89
  /**
@@ -69,14 +100,14 @@ export declare function classifyPaste(chunk: string, thresholds?: PasteThreshold
69
100
  /**
70
101
  * The immutable marker → body store.
71
102
  *
72
- * `bodies` maps each minted marker token to the exact text it hides; `minted`
103
+ * `bodies` maps each ordinal number to the exact text it hides; `minted`
73
104
  * is the running count that drives the next ordinal. The store is replaced (not
74
105
  * mutated) on every coalesce, so the surface holds it as a plain value and React
75
106
  * reconciliation stays correct.
76
107
  */
77
108
  export interface PasteVault {
78
- /** Marker token → the original pasted text it stands for. */
79
- readonly bodies: ReadonlyMap<string, string>;
109
+ /** Ordinal number → the original pasted text it stands for. */
110
+ readonly bodies: ReadonlyMap<number, string>;
80
111
  /** How many markers have been minted so far (the next ordinal is this + 1). */
81
112
  readonly minted: number;
82
113
  }
@@ -106,6 +137,27 @@ export interface PasteCapture {
106
137
  * @param body the full pasted text to hide
107
138
  */
108
139
  export declare function coalescePaste(body: string, vault?: PasteVault): PasteCapture;
140
+ /**
141
+ * Grow an existing paste body with a follow-on chunk, reminting its marker.
142
+ *
143
+ * The continuation path for a paste the terminal split across reads: the extra
144
+ * text is appended to `ordinal`'s stored body and a fresh marker (same ordinal,
145
+ * updated line count) is returned so the surface can swap the token already
146
+ * spliced into the buffer. `minted` is unchanged — no new ordinal is consumed.
147
+ * An unknown ordinal starts from an empty body, so the call never throws.
148
+ *
149
+ * @param ordinal the marker ordinal to grow
150
+ * @param extra the follow-on pasted text to append
151
+ * @param vault the store holding the ordinal's current body
152
+ */
153
+ export declare function appendToPaste(ordinal: number, extra: string, vault: PasteVault): PasteCapture;
154
+ /**
155
+ * The line count of an ordinal's stored body — the surface consults this
156
+ * against {@link PASTE_MARKER_LINE_CAP} to decide whether a continuation chunk
157
+ * may still append or must roll over into a fresh marker. Zero for an unknown
158
+ * ordinal or an empty body.
159
+ */
160
+ export declare function pasteLines(vault: PasteVault, ordinal: number): number;
109
161
  /**
110
162
  * Rewrite every paste marker in a buffer back to the text it stands for.
111
163
  *
@@ -15,7 +15,7 @@
15
15
  * abort/clear settles every outstanding entry with `"deny"` so a cancelled turn
16
16
  * never hangs on a pending prompt.
17
17
  */
18
- import type { ApprovalChoice } from "../../conductor";
18
+ import type { ApprovalChoice, PermissionMode } from "../../conductor";
19
19
  import type { ApprovalRequest } from "./approval";
20
20
  /** One parked approval request awaiting the user's choice. */
21
21
  export interface ApprovalEntry {
@@ -69,3 +69,20 @@ export declare function approvalQueueReducer(state: ApprovalQueueState, event: A
69
69
  * @param state the queue state
70
70
  */
71
71
  export declare function activeApproval(state: ApprovalQueueState): ApprovalEntry | undefined;
72
+ /**
73
+ * Whether switching INTO `mode` mid-prompt should release (auto-allow) every
74
+ * PARKED approval request.
75
+ *
76
+ * Only `bypass` qualifies: a parked entry already passed the deny-rule and
77
+ * catastrophic-blocklist scans before it reached `ask`, and under bypass the
78
+ * engine allows everything not denied — so settling the parked prompts with
79
+ * `allow-once` matches exactly what the mode now means, and the run resumes
80
+ * instead of idling behind a prompt the user just declared moot. Every other
81
+ * mode keeps the prompts parked: `default`/`acceptEdits` still want the answer,
82
+ * and `plan` must not auto-ALLOW a mutating call (the user can still deny).
83
+ *
84
+ * Pure; the component wires it to the actual settle.
85
+ *
86
+ * @param mode the permission mode just switched into
87
+ */
88
+ export declare function modeReleasesApprovals(mode: PermissionMode): boolean;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Saffron picker boards — the themed `/settings` and `/model` overlays styled to
3
+ * the "IndusCode Saffron · Settings · Config" design.
4
+ *
5
+ * The framework ships plain (un-themed) `SettingsDialog` / `ModelDialog`. These
6
+ * boards are the console's own theme-aware replacements for those two pickers: a
7
+ * saffron tab-pill header, a teal-bordered search box with a magnifier and a
8
+ * block caret, a two-column list whose values are tinted by kind (on/true →
9
+ * green, off/false → dim, everything else → teal), a windowed "↓ N more" hint,
10
+ * and a top-ruled footer of key hints. They reuse the framework's pure
11
+ * {@link matchesSearchQuery} / {@link wrapSelectionIndex} helpers (and the
12
+ * {@link SettingsDialogItem} shape) so only the *presentation* is re-authored,
13
+ * not the filter/navigation logic.
14
+ *
15
+ * All colour comes through the framework {@link InkThemeAdapter} the overlay
16
+ * group already holds, so the boards recolour with the active scheme; the one
17
+ * raw-hex touch is the chalk tab-pill (dark text on the accent fill), which has
18
+ * no theme role to resolve against.
19
+ */
20
+ import { type InkThemeAdapter, type SettingsDialogItem } from "indusagi/react-ink";
21
+ import type { Model } from "indusagi/ai";
22
+ /** What the {@link SettingsBoard} renders. */
23
+ export interface SettingsBoardProps {
24
+ /** The framework adapter that turns token roles into terminal colours. */
25
+ readonly theme: InkThemeAdapter;
26
+ /** The settings rows to present and cycle. */
27
+ readonly items: readonly SettingsDialogItem[];
28
+ /** Invoked when the board is dismissed. */
29
+ readonly onClose: () => void;
30
+ }
31
+ /**
32
+ * The saffron `/settings` board: a filterable, value-cycling settings list in the
33
+ * design's look. Up/Down move (wrapping), Left/Right or Enter cycle the value,
34
+ * typing filters, Ctrl+U clears, Esc closes.
35
+ */
36
+ export declare function SettingsBoard({ theme, items, onClose }: SettingsBoardProps): JSX.Element;
37
+ /** What the {@link ModelBoard} renders. */
38
+ export interface ModelBoardProps {
39
+ /** The framework adapter that turns token roles into terminal colours. */
40
+ readonly theme: InkThemeAdapter;
41
+ /** The selectable models. */
42
+ readonly models: readonly Model<any>[];
43
+ /** Optional initial search query. */
44
+ readonly search?: string;
45
+ /** Invoked with the chosen model. */
46
+ readonly onSelect: (model: Model<any>) => void | Promise<void>;
47
+ /** Invoked when the board is dismissed. */
48
+ readonly onClose: () => void;
49
+ }
50
+ /**
51
+ * The saffron `/model` board: a filterable model list in the design's look.
52
+ * Up/Down move (wrapping), Enter selects, typing filters, Ctrl+U clears, Esc
53
+ * closes.
54
+ */
55
+ export declare function ModelBoard({ theme, models, onClose, onSelect, search }: ModelBoardProps): JSX.Element;
@@ -12,4 +12,4 @@ export { PickerOverlays, type OverlayGroupProps } from "./pickers";
12
12
  export { SessionOverlays } from "./sessions";
13
13
  export { AuthOverlays } from "./auth";
14
14
  export { ApprovalOverlays, choiceFromId, readApprovalPayload, summarizeInput, APPROVAL_CHOICES, DISMISS_CHOICE, type ApprovalRequest, type ApprovalPayload, } from "./approval";
15
- export { approvalQueueReducer, activeApproval, EMPTY_APPROVAL_QUEUE, type ApprovalEntry, type ApprovalQueueState, type ApprovalQueueEvent, } from "./approval-queue";
15
+ export { approvalQueueReducer, activeApproval, modeReleasesApprovals, EMPTY_APPROVAL_QUEUE, type ApprovalEntry, type ApprovalQueueState, type ApprovalQueueEvent, } from "./approval-queue";
@@ -34,7 +34,7 @@ import type { InkThemeAdapter, ThemeTokens } from "../contract";
34
34
  * accent ← signal (primary accent the components highlight in)
35
35
  * borderMuted ← quietFrame (de-emphasised borders/separators)
36
36
  * bashBorder ← frame (the bash-block border tone)
37
- * userMessage ← promptSurface (user-turn background tint)
37
+ * userMessage ← "" (no background, plain text)
38
38
  * customMessage ← cardAccent (custom/plugin message background tint)
39
39
  * success ← affirm
40
40
  * warning ← caution
@@ -12,7 +12,7 @@
12
12
  * Consumers import from `src/console/theme` and never reach into the individual
13
13
  * palette/tokens/adapter/resolve modules.
14
14
  */
15
- export { MIDNIGHT_PALETTE, DAYLIGHT_PALETTE, MIDNIGHT_CB_PALETTE, DAYLIGHT_CB_PALETTE, PALETTES, } from "./palette";
15
+ export { SAFFRON_PALETTE, MIDNIGHT_PALETTE, DAYLIGHT_PALETTE, MIDNIGHT_CB_PALETTE, DAYLIGHT_CB_PALETTE, PALETTES, } from "./palette";
16
16
  export { deriveTokens } from "./tokens";
17
17
  export { themeAdapter, frameworkColors } from "./adapter";
18
18
  export { THEMES, THEME_SCHEMES, resolveTheme } from "./resolve";
@@ -33,6 +33,16 @@ export declare const MIDNIGHT_PALETTE: ThemePalette;
33
33
  * to a dark-on-light slate.
34
34
  */
35
35
  export declare const DAYLIGHT_PALETTE: ThemePalette;
36
+ /**
37
+ * The "IndusCode Saffron" dark-terminal ramp — the brand scheme.
38
+ *
39
+ * The console's signature look: a warm saffron primary (borders, headings, the
40
+ * brand wordmark) against a cream neutral gradient, with a cool teal secondary
41
+ * for the interactive accents (the prompt caret, chips, bullets) — the two hues
42
+ * the IndusCode masthead is built from. The status hues stay warm-leaning so the
43
+ * whole scheme reads as one saffron identity rather than a recoloured midnight.
44
+ */
45
+ export declare const SAFFRON_PALETTE: ThemePalette;
36
46
  /**
37
47
  * The dark-terminal color-blind-safe ramp.
38
48
  *
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Login command — `runLogin`.
3
+ *
4
+ * The top-level `login` / `logout` / `whoami` surface that authenticates the CLI
5
+ * against the indus-server so a user with **no local provider key** can still run
6
+ * model turns through the server gateway (the session token is vended as the
7
+ * provider key downstream — see `src/boot/runners/server-mode.ts`).
8
+ *
9
+ * `login` drives the OAuth 2.0 **device-authorization** flow (RFC 8628) against
10
+ * the server's better-auth endpoints under `${INDUS_SERVER_URL}/api/auth`:
11
+ *
12
+ * 1. POST `/api/auth/device/code` → a `user_code` + verification URL.
13
+ * 2. The user opens the URL in a browser and approves the device.
14
+ * 3. We poll POST `/api/auth/device/token` until it returns an `access_token`.
15
+ * 4. The token is persisted via {@link storeServerToken} (A1's token store).
16
+ *
17
+ * This is a **pre-pipeline router**, not a runner: a no-key user cannot build a
18
+ * session, so login must short-circuit before the stage pipeline. {@link runLogin}
19
+ * therefore mirrors `runCredential` in `boot.ts` — it owns the first positional
20
+ * token only and returns `undefined` (fall through to a normal launch) when that
21
+ * token is not one of its verbs.
22
+ *
23
+ * The app does not depend on `better-auth/client`, `open`, or a spinner library,
24
+ * so the device flow is implemented over raw `fetch` and the console is driven
25
+ * with `chalk` to match the rest of the launch surface. `--server-url` and
26
+ * `--client-id` are parsed from the argv tail here (the declarative flag table is
27
+ * for the launch invocation, not these auth-only verbs).
28
+ */
29
+ import type { OAuthLoginCallbacks } from "indusagi/ai";
30
+ import type { BootContext } from "../boot/contract";
31
+ /**
32
+ * The sign-in directory id for the indus-server session (as opposed to a
33
+ * per-model-provider credential). Used by `src/launch/oauth.ts` to special-case
34
+ * this entry in `listLoginProviders` / `startOAuthLogin` so the interactive
35
+ * `/login` picker can drive the same device-authorization flow as the `indus
36
+ * login` CLI verb.
37
+ */
38
+ export declare const SERVER_TIER_PROVIDER_ID = "indus-server";
39
+ /**
40
+ * Drive the indus-server device-authorization flow through the framework's
41
+ * generic {@link OAuthLoginCallbacks} shape, so the interactive `/login` picker
42
+ * (`src/console/overlays/auth.tsx`'s `OAuthFlowDialog`) can establish a
43
+ * server-tier session the same way the `indus login` CLI verb does.
44
+ *
45
+ * Reuses the private {@link requestDeviceCode} / {@link pollForToken} helpers
46
+ * above rather than duplicating the request+poll sequence, and reports the
47
+ * verification URL / code through `callbacks.onAuth` exactly once, mirroring
48
+ * how `doLogin` renders them to the console. On success the token is persisted
49
+ * via {@link storeServerToken}; any thrown error (network failure, denial,
50
+ * expiry, timeout) propagates to the caller unchanged — `startOAuthLogin`'s
51
+ * caller (the dialog's try/catch) already renders a generic failure state.
52
+ *
53
+ * @param callbacks the framework's OAuth login callbacks (url + progress)
54
+ * @param serverUrl the indus-server base url (defaults to {@link INDUS_SERVER_URL})
55
+ * @param clientId the device-flow client id (defaults to {@link INDUS_CLIENT_ID})
56
+ */
57
+ export declare function runServerTierLogin(callbacks: OAuthLoginCallbacks, serverUrl?: string, clientId?: string): Promise<void>;
58
+ /**
59
+ * Route a `login` / `logout` / `whoami` verb, if this invocation is one.
60
+ *
61
+ * Inspects `ctx.argv[0]`: returns a numeric exit code when it handled one of the
62
+ * auth verbs, or `undefined` when the leading token is something else so the
63
+ * caller falls through to a normal launch. Modeled on `runCredential` in
64
+ * `boot.ts`; `--server-url` / `--client-id` are parsed from the argv tail.
65
+ *
66
+ * @param ctx the seeded boot context (carries the raw argv)
67
+ */
68
+ export declare function runLogin(ctx: BootContext): Promise<number | undefined>;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `src/launch/oauth.ts` — unit tests for the indus-server bridge added to the
3
+ * merged sign-in directory and `startOAuthLogin`.
4
+ *
5
+ * Covers:
6
+ * 1. {@link listLoginProviders} includes the `indus-server` entry
7
+ * (`SERVER_TIER_PROVIDER_ID`, `authKind: "oauth"`).
8
+ * 2. {@link startOAuthLogin} for `SERVER_TIER_PROVIDER_ID` drives the device
9
+ * flow over a mocked `fetch`, reports `onAuth` exactly once with the right
10
+ * shape, reports progress, never touches the vault, and resolves.
11
+ * 3. {@link startOAuthLogin} for an unregistered id still throws the
12
+ * pre-existing "No browser sign-in is available" error (regression check).
13
+ * 4. A device/token failure (`access_denied`) propagates out of
14
+ * `startOAuthLogin` unchanged rather than being swallowed.
15
+ *
16
+ * `storeServerToken` is mocked (partial mock of `../boot/server-token`) so the
17
+ * success-path test never touches the real `~/.indusagi/agent/server-token.json`
18
+ * file on the machine running the suite.
19
+ */
20
+ export {};
@@ -38,6 +38,12 @@ export interface SummarizeDeps {
38
38
  readonly priorDigest?: string;
39
39
  /** Cancellation signal forwarded to the completer. */
40
40
  readonly signal?: AbortSignal;
41
+ /**
42
+ * Credential for the summarization model's provider, forwarded to the
43
+ * completer. Required in practice for sessions whose key is resolved
44
+ * per-call (server tier) rather than from an environment variable.
45
+ */
46
+ readonly apiKey?: string;
41
47
  /**
42
48
  * Optional cap on the digest size, in tokens, forwarded to the completer's
43
49
  * `maxTokens`. A modest ceiling keeps the summary itself from eating the