indusagi-coding-agent 0.1.62 → 0.2.1

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 (67) hide show
  1. package/dist/entry.js +5395 -1509
  2. package/dist/guardrails.js +2031 -0
  3. package/dist/index.js +18371 -0
  4. package/dist/types/boot/contract.d.ts +2 -0
  5. package/dist/types/boot/index.d.ts +1 -1
  6. package/dist/types/boot/runners/addon-wiring.d.ts +103 -0
  7. package/dist/types/boot/runners/addon-wiring.test.d.ts +19 -0
  8. package/dist/types/boot/runners/checkpoint.d.ts +133 -0
  9. package/dist/types/boot/runners/checkpoint.test.d.ts +12 -0
  10. package/dist/types/boot/runners/delegate-runner.d.ts +83 -0
  11. package/dist/types/boot/runners/delegate-runner.test.d.ts +13 -0
  12. package/dist/types/boot/runners/index.d.ts +2 -0
  13. package/dist/types/boot/runners/memdir.d.ts +103 -0
  14. package/dist/types/boot/runners/memdir.test.d.ts +12 -0
  15. package/dist/types/boot/runners/read-state.d.ts +82 -0
  16. package/dist/types/boot/runners/read-state.test.d.ts +10 -0
  17. package/dist/types/boot/runners/session.d.ts +37 -2
  18. package/dist/types/boot/runners/session.test.d.ts +10 -0
  19. package/dist/types/briefing/context-docs.d.ts +38 -0
  20. package/dist/types/briefing/context-docs.test.d.ts +18 -0
  21. package/dist/types/briefing/index.d.ts +2 -0
  22. package/dist/types/capability-deck/cards/index.d.ts +6 -0
  23. package/dist/types/capability-deck/cards/memory-card.d.ts +9 -10
  24. package/dist/types/capability-deck/cards/plan-file.d.ts +56 -0
  25. package/dist/types/capability-deck/cards/plan-tools.d.ts +97 -0
  26. package/dist/types/capability-deck/cards/plan-tools.test.d.ts +9 -0
  27. package/dist/types/capability-deck/checkpoint.int.test.d.ts +25 -0
  28. package/dist/types/capability-deck/index.d.ts +1 -1
  29. package/dist/types/capability-deck/read-edit-gate.int.test.d.ts +21 -0
  30. package/dist/types/conductor/bash-guard.d.ts +106 -0
  31. package/dist/types/conductor/bash-guard.test.d.ts +17 -0
  32. package/dist/types/conductor/conductor.d.ts +37 -6
  33. package/dist/types/conductor/contract.d.ts +214 -2
  34. package/dist/types/conductor/diagnostics.d.ts +183 -0
  35. package/dist/types/conductor/diagnostics.test.d.ts +10 -0
  36. package/dist/types/conductor/index.d.ts +4 -1
  37. package/dist/types/conductor/permission-gate.integration.test.d.ts +22 -0
  38. package/dist/types/conductor/permission-wiring.test.d.ts +14 -0
  39. package/dist/types/conductor/permissions.d.ts +217 -0
  40. package/dist/types/conductor/permissions.test.d.ts +12 -0
  41. package/dist/types/conductor/plan-mode.integration.test.d.ts +23 -0
  42. package/dist/types/conductor/post-edit-diagnostics.test.d.ts +13 -0
  43. package/dist/types/conductor/transcript-store/serialize.test.d.ts +10 -0
  44. package/dist/types/conductor/transcript-store/store.d.ts +18 -0
  45. package/dist/types/console/components/StatusBar.d.ts +14 -3
  46. package/dist/types/console/components/WorkingIndicator.d.ts +44 -0
  47. package/dist/types/console/components/WorkingIndicator.test.d.ts +9 -0
  48. package/dist/types/console/contract.d.ts +2 -1
  49. package/dist/types/console/input/keymap.d.ts +10 -1
  50. package/dist/types/console/overlays/approval-queue.d.ts +71 -0
  51. package/dist/types/console/overlays/approval.d.ts +104 -0
  52. package/dist/types/console/overlays/approval.test.d.ts +17 -0
  53. package/dist/types/console/overlays/host.d.ts +4 -3
  54. package/dist/types/console/overlays/index.d.ts +2 -0
  55. package/dist/types/guardrails.d.ts +33 -0
  56. package/dist/types/launch/contract.d.ts +2 -0
  57. package/dist/types/launch/index.d.ts +1 -1
  58. package/dist/types/launch/oauth.d.ts +13 -0
  59. package/dist/types/settings/contract.d.ts +47 -0
  60. package/dist/types/settings/index.d.ts +2 -2
  61. package/dist/types/window-budget/condenser.d.ts +15 -1
  62. package/dist/types/window-budget/index.d.ts +3 -1
  63. package/dist/types/window-budget/microcompact.d.ts +68 -0
  64. package/dist/types/window-budget/microcompact.test.d.ts +16 -0
  65. package/dist/types/window-budget/rehydrate.d.ts +56 -0
  66. package/dist/types/workspace/brand.d.ts +1 -1
  67. package/package.json +14 -3
@@ -34,10 +34,14 @@
34
34
  *
35
35
  * The conductor never re-declares these; it composes them.
36
36
  */
37
- import type { AgentMessage, AgentTool, ThinkingLevel } from "indusagi/agent";
37
+ import type { AgentMessage, AgentTool, CanUseToolFn, ThinkingLevel } from "indusagi/agent";
38
38
  import type { KnownProvider, Model, Usage } from "indusagi/ai";
39
+ import type { PermissionMode } from "../settings";
40
+ import type { ApprovalResolver } from "./permissions";
39
41
  /** Re-exported framework vocabulary that conductor consumers routinely need. */
40
- export type { AgentMessage, AgentTool, ThinkingLevel, Model, Usage, KnownProvider };
42
+ export type { AgentMessage, AgentTool, CanUseToolFn, ThinkingLevel, Model, Usage, KnownProvider };
43
+ /** Re-exported permission vocabulary, so conductor consumers get it in one import. */
44
+ export type { PermissionMode, ApprovalResolver };
41
45
  /**
42
46
  * The closed set of failure categories the conductor can surface.
43
47
  *
@@ -203,6 +207,13 @@ export interface SessionHead {
203
207
  readonly sessionId: string;
204
208
  /** Id of the active leaf node, or `null` for an empty transcript. */
205
209
  readonly leaf: string | null;
210
+ /**
211
+ * Cumulative session usage (tokens + cost) persisted alongside the head so
212
+ * resume can restore the running total instead of seeding zero. Optional and
213
+ * absent on legacy transcripts; the store rewrites the head line on every
214
+ * flush, keeping this current with the live tally.
215
+ */
216
+ readonly usage?: Usage;
206
217
  }
207
218
  /**
208
219
  * A lightweight, resolved reference to one model card in the catalog.
@@ -348,6 +359,42 @@ export interface BashOutcome {
348
359
  /** Process exit code (`0` on success; non-zero, or `1` on a thrown error). */
349
360
  readonly exitCode: number;
350
361
  }
362
+ /**
363
+ * The minimal file-checkpoint surface the conductor drives for rewind (#24).
364
+ *
365
+ * The product mints a concrete `CheckpointStore` (in `boot/runners/checkpoint.ts`),
366
+ * injects it into the deck's `ctx.framework` bag under the `'checkpoint'` key so
367
+ * the framework's write/edit tools record pre-mutation file content against the
368
+ * active transcript node, and ALSO hands it to the conductor as this port. The
369
+ * conductor pins the active node id as its head advances ({@link setActiveNodeId})
370
+ * so a turn's edits key to the node that was active before the turn, and exposes
371
+ * {@link restore}/{@link hasSnapshot} so the tree picker can roll the working tree
372
+ * back when navigating to an earlier node.
373
+ *
374
+ * Declared as a tiny structural port (not the concrete store) so the conductor
375
+ * stays free of any boot-layer import — the product's store satisfies it by shape.
376
+ */
377
+ export interface CheckpointPort {
378
+ /**
379
+ * Pin the transcript node subsequent file snapshots are filed under. Called by
380
+ * the conductor as its head advances (at turn start) so a turn's edits key to
381
+ * the node active before the turn ran.
382
+ *
383
+ * @param id the active transcript node id, or `null` to fall back to the root
384
+ */
385
+ setActiveNodeId(id: string | null): void;
386
+ /** Whether a node has ANY recorded file snapshot (the picker's restore gate). */
387
+ hasSnapshot(nodeId: string): boolean;
388
+ /**
389
+ * Roll the working tree back to a node's recorded state, rewriting each tracked
390
+ * file to its pre-mutation content (deleting files recorded as absent). A safe
391
+ * no-op when the node has no snapshot.
392
+ *
393
+ * @param nodeId the transcript node whose file state to restore to
394
+ * @returns the absolute paths that were written or deleted
395
+ */
396
+ restore(nodeId: string): string[];
397
+ }
351
398
  /**
352
399
  * Options that configure a {@link SessionConductor} at assembly time.
353
400
  *
@@ -365,6 +412,14 @@ export interface SessionConductorOptions {
365
412
  readonly tools?: AgentTool[];
366
413
  /** Initial reasoning effort for models that support it. */
367
414
  readonly thinking?: ThinkingLevel;
415
+ /**
416
+ * Canonical id of a model to fall back to when the bound model is overloaded
417
+ * (HTTP 529 / "overloaded") mid-turn (`--fallback-model`). When set, an
418
+ * overload that exhausts the transient-retry budget swaps to this model once
419
+ * per turn and retries instead of surfacing a terminal fault. Absent disables
420
+ * the swap entirely (behavior-preserving default).
421
+ */
422
+ readonly fallbackModelId?: string;
368
423
  /** Working directory the session is scoped to (defaults to process cwd). */
369
424
  readonly workspace?: string;
370
425
  /**
@@ -385,6 +440,53 @@ export interface SessionConductorOptions {
385
440
  * environment lookup. May be sync or async.
386
441
  */
387
442
  readonly getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
443
+ /**
444
+ * The hard per-tool permission gate, threaded straight to the framework `Agent`.
445
+ * The framework awaits it on the validated arguments immediately before each
446
+ * tool runs and either proceeds (optionally with substituted input) or
447
+ * short-circuits to an `isError` tool result.
448
+ *
449
+ * Supplied as a **factory** `(currentMode, requestApproval?) => CanUseToolFn`:
450
+ * the conductor calls it once with a getter onto its OWN live permission mode,
451
+ * so the gate it returns reads `permissionMode()` live — a
452
+ * {@link SessionConductor.setPermissionMode} retargets later tool calls with no
453
+ * agent rebuild. (A caller that does not care about the live mode can simply
454
+ * ignore the getter and close over a fixed gate.)
455
+ *
456
+ * The OPTIONAL second argument is a stable {@link ApprovalResolver} delegate the
457
+ * conductor owns: it forwards to whatever resolver was installed via
458
+ * {@link SessionConductor.setApprovalResolver} at call time (and denies when none
459
+ * is installed). An interactive front-end can therefore wire its approval overlay
460
+ * AFTER the conductor (and its gate) are built — the factory just closes over the
461
+ * delegate. A factory that ignores it keeps today's behavior.
462
+ *
463
+ * Optional everywhere: omit it and the framework allows every tool (today's
464
+ * allow-all behavior).
465
+ */
466
+ readonly canUseTool?: (currentMode: () => PermissionMode, requestApproval?: ApprovalResolver) => CanUseToolFn;
467
+ /**
468
+ * The permission mode the session opens in. Seeds {@link SessionConductor.permissionMode};
469
+ * defaults to `"default"` when absent. The mode is consulted live by the
470
+ * {@link canUseTool} gate, so a later {@link SessionConductor.setPermissionMode}
471
+ * changes subsequent tool calls without rebuilding the gate.
472
+ */
473
+ readonly permissionMode?: PermissionMode;
474
+ /**
475
+ * Directory the approved plan-mode plan is persisted into. When the model calls
476
+ * `exit_plan_mode` and the user approves leaving plan mode, the conductor writes
477
+ * the plan as a slug-named markdown file under `<plansDir>/plans/`. Absent keeps
478
+ * the handshake (mode flip + context injection) but skips the disk write —
479
+ * tests and headless probes can run the round-trip without a filesystem.
480
+ */
481
+ readonly plansDir?: string;
482
+ /**
483
+ * The per-session file-checkpoint store for rewind (#24), or `undefined` to run
484
+ * without code checkpointing. When present, the conductor pins the active
485
+ * transcript node on it (the head leaf at turn start) so a turn's file edits key
486
+ * to the node that was active before the turn, and {@link SessionConductor.restoreCode}
487
+ * delegates to it so the tree picker can revert the working tree.
488
+ */
489
+ readonly checkpoint?: CheckpointPort;
388
490
  }
389
491
  /**
390
492
  * The conductor of a single coding-agent session.
@@ -460,6 +562,19 @@ export interface SessionConductor {
460
562
  * @param id canonical id of the model to switch to
461
563
  */
462
564
  selectModel(id: string): void;
565
+ /**
566
+ * Replace the agent's tool deck for subsequent turns.
567
+ *
568
+ * Used by `/mcp` to inject the tools of freshly-connected MCP servers into the
569
+ * live session (and to drop them again on disconnect). The conductor merges the
570
+ * passed list with its own seed deck (the built-in tools the session was
571
+ * assembled with), so callers pass only the *extra* tools to add — never the
572
+ * built-ins, which are preserved automatically. Pass `[]` to clear the extras
573
+ * and fall back to the seed deck alone.
574
+ *
575
+ * @param tools the additional tools to layer over the seed deck
576
+ */
577
+ registerTools(tools: AgentTool[]): void;
463
578
  /**
464
579
  * Manually run the same transcript-condense path the auto-compactor uses,
465
580
  * emitting the existing `compacted` signal. Safe to call when idle; a no-op
@@ -482,6 +597,29 @@ export interface SessionConductor {
482
597
  * @param nodeId the transcript node to make the active leaf
483
598
  */
484
599
  navigateTree(nodeId: string): Promise<void>;
600
+ /**
601
+ * Roll the working tree back to a transcript node's file-checkpoint state
602
+ * (rewind, #24). Reverts every file the node tracked to its pre-mutation content
603
+ * (deleting files that were absent at that point). Additive and code-only: it
604
+ * does NOT move the conversation head — pair it with {@link navigateTree}/{@link fork}
605
+ * for a combined "restore code and conversation". A safe no-op when no checkpoint
606
+ * store is wired or the node has no recorded snapshot.
607
+ *
608
+ * @param nodeId the transcript node whose file state to restore to
609
+ * @returns the absolute paths that were restored (written or deleted)
610
+ */
611
+ restoreCode(nodeId: string): Promise<string[]>;
612
+ /**
613
+ * Whether the tree picker should offer "restore code" for a node — i.e. whether
614
+ * the node has any recorded file-checkpoint snapshot. `false` when no checkpoint
615
+ * store is wired or the node never mutated files, so the picker's restore option
616
+ * is a safe no-op there.
617
+ *
618
+ * @param nodeId the transcript node to test
619
+ */
620
+ codeRestoreInfo(nodeId: string): Promise<{
621
+ readonly canRestore: boolean;
622
+ }>;
485
623
  /**
486
624
  * Run a shell command in the session workspace, returning its combined
487
625
  * stdout+stderr and exit code. Unless `opts.excludeFromContext` is set, the
@@ -494,6 +632,12 @@ export interface SessionConductor {
494
632
  executeBash(command: string, opts?: ExecuteBashOptions): Promise<BashOutcome>;
495
633
  /** A point-in-time {@link SessionStats} tally for the active session. */
496
634
  stats(): SessionStats;
635
+ /**
636
+ * Render the session's cumulative cost + token usage as a human-readable,
637
+ * multi-line report. Drives the `/cost` slash command without the caller
638
+ * having to format the {@link SessionStats} figures itself.
639
+ */
640
+ costReport(): string;
497
641
  /** The reasoning effort currently applied to the session. */
498
642
  thinkingLevel(): ThinkingLevel;
499
643
  /**
@@ -508,6 +652,74 @@ export interface SessionConductor {
508
652
  * return the newly-selected level.
509
653
  */
510
654
  cycleThinkingLevel(): ThinkingLevel;
655
+ /**
656
+ * The permission mode currently in effect. The live `canUseTool` gate reads
657
+ * this on every tool call, so the value reflects the most recent
658
+ * {@link setPermissionMode}.
659
+ */
660
+ permissionMode(): PermissionMode;
661
+ /**
662
+ * Switch the permission mode for subsequent tool calls. The gate consults the
663
+ * mode live (via a getter), so this takes effect on the next tool call with no
664
+ * agent rebuild. A no-op-equivalent when no gate was wired (the conductor still
665
+ * tracks the mode for the UI).
666
+ *
667
+ * @param mode the permission mode to apply
668
+ */
669
+ setPermissionMode(mode: PermissionMode): void;
670
+ /**
671
+ * Install (or clear) the host approval resolver an `ask` decision routes to.
672
+ *
673
+ * The `canUseTool` gate consults a STABLE delegate the conductor owns; this
674
+ * method swaps the resolver behind that delegate, so an interactive front-end
675
+ * can wire its approval overlay AFTER the conductor (and its gate) are built —
676
+ * the exact post-mount install the React console needs. Passing `undefined`
677
+ * clears it (an `ask` then deterministically denies, the non-interactive
678
+ * default). A no-op-equivalent when no gate was wired; the resolver is simply
679
+ * never consulted.
680
+ *
681
+ * @param resolver the resolver to consult on an `ask`, or `undefined` to clear
682
+ */
683
+ setApprovalResolver(resolver: ApprovalResolver | undefined): void;
684
+ /**
685
+ * Toggle plan mode on or off.
686
+ *
687
+ * Entering plan mode (`true`) captures the current mode as the pre-plan mode and
688
+ * switches to `"plan"` (read-only — the gate blocks every mutating tool).
689
+ * Leaving (`false`) restores the captured pre-plan mode (or `"default"` when none
690
+ * was captured). Idempotent: toggling on while already in plan mode is a no-op,
691
+ * and toggling off when not in plan mode restores/keeps `"default"`. Drives the
692
+ * `/plan` command and the Shift+Tab toggle, and is the same path the conductor's
693
+ * own `enter_plan_mode` / approved `exit_plan_mode` handshake uses.
694
+ *
695
+ * @param on `true` to enter plan mode, `false` to leave it
696
+ * @returns the permission mode in effect after the toggle
697
+ */
698
+ togglePlanMode(on: boolean): PermissionMode;
699
+ /**
700
+ * Advance the permission mode to the NEXT mode in the fixed cycle and return the
701
+ * new mode. The order is `default → acceptEdits → plan → bypass → (wrap)
702
+ * default`; the `"bypassPermissions"` alias is folded to `"bypass"` when reading
703
+ * the current mode so the cycle is stable.
704
+ *
705
+ * Built on top of {@link setPermissionMode}, so the live `canUseTool` gate (which
706
+ * reads {@link permissionMode} on every call) picks the new mode up immediately
707
+ * with no agent rebuild. Plan bookkeeping is preserved: ENTERING `"plan"` captures
708
+ * the pre-plan mode exactly as {@link togglePlanMode} does, so a later round-trip
709
+ * restores the prior mode; LEAVING `"plan"` (cycling `plan → bypass`) is a plain
710
+ * mode change — it does NOT run the `exit_plan_mode` approval handshake, since a
711
+ * manual cycle is not a plan exit. Drives the Shift+Tab cycle; {@link togglePlanMode}
712
+ * remains the path `/plan` uses.
713
+ *
714
+ * @returns the permission mode in effect after the advance
715
+ */
716
+ cyclePermissionMode(): PermissionMode;
717
+ /**
718
+ * The mode captured when plan mode was last entered, restored on exit — or
719
+ * `undefined` when not currently in (or paused from) plan mode. Exposed so a UI
720
+ * (and the round-trip tests) can observe the Enter/Exit pairing.
721
+ */
722
+ prePlanMode(): PermissionMode | undefined;
511
723
  /** The human-readable session name, or `undefined` when none is set. */
512
724
  sessionName(): string | undefined;
513
725
  /**
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Post-edit live diagnostics — an LSP-free, shell-sourced baseline-diff engine.
3
+ *
4
+ * The full induscode story runs a long-lived LSP and pulls structured
5
+ * diagnostics off the language server. That stack is out of scope for the
6
+ * clean-room product, so this is the **pragmatic shell path**: after a turn that
7
+ * edited TypeScript/JS files, the conductor asks this engine to run a project
8
+ * checker (`tsc --noEmit`, plus `eslint --format json` when an eslint config is
9
+ * present), scoped to the workspace, and diff the result against a *per-session
10
+ * baseline* captured before the edits. Only the diagnostics that are genuinely
11
+ * **new** (not in the baseline, not already delivered in a prior turn) are
12
+ * surfaced, and they are injected into the NEXT agent turn as a follow-up note.
13
+ *
14
+ * Everything here is best-effort and non-fatal:
15
+ * - the checker spawns its OWN child process (lazy `node:child_process`), so
16
+ * the module stays importable in a non-Node test context;
17
+ * - a missing/slow/erroring checker NEVER faults the turn — the run is bounded
18
+ * by an {@link AbortController} timeout and every failure resolves to `[]`;
19
+ * - results are deduplicated and volume-capped (10/file, 30 total,
20
+ * severity-sorted) so a broken project can't dump hundreds of lines into
21
+ * context;
22
+ * - it is fully opt-out-able (`DiagnosticsConfig.enabled === false`) and
23
+ * defaults OFF when the workspace has no `tsconfig.json`.
24
+ *
25
+ * The line/column convention mirrors the LSP `Diagnostic.range`: 0-based.
26
+ * `tsc` reports 1-based positions, so {@link runTscDiagnostics} subtracts 1 and
27
+ * {@link formatDiagnosticsSummary} re-adds 1 when rendering for a human/agent.
28
+ */
29
+ /** Severity rank, ordered most→least urgent for the volume cap's sort. */
30
+ export type DiagnosticSeverity = "Error" | "Warning" | "Info" | "Hint";
31
+ /** A zero-based line/column position. */
32
+ export interface Position {
33
+ readonly line: number;
34
+ readonly character: number;
35
+ }
36
+ /** A single diagnostic, shaped after the LSP `Diagnostic` (0-based range). */
37
+ export interface Diagnostic {
38
+ readonly message: string;
39
+ readonly severity: DiagnosticSeverity;
40
+ readonly range: {
41
+ readonly start: Position;
42
+ readonly end: Position;
43
+ };
44
+ readonly source?: string;
45
+ readonly code?: string | number;
46
+ }
47
+ /** All diagnostics for one file, keyed by an absolute fs path used as the uri. */
48
+ export interface DiagnosticFile {
49
+ readonly uri: string;
50
+ readonly diagnostics: Diagnostic[];
51
+ }
52
+ /** Max diagnostics surfaced for any single file (mirrors the LSP registry cap). */
53
+ export declare const MAX_PER_FILE = 10;
54
+ /** Max diagnostics surfaced across all files in one turn. */
55
+ export declare const MAX_TOTAL = 30;
56
+ /** Char budget for the rendered summary (truncated past this). */
57
+ export declare const MAX_SUMMARY_CHARS = 4000;
58
+ /**
59
+ * A stable identity for one diagnostic: position + code + (trimmed) message.
60
+ * Two diagnostics with the same key are "the same problem" for dedup/baseline
61
+ * purposes even across separate checker runs.
62
+ */
63
+ export declare function createDiagnosticKey(d: Diagnostic): string;
64
+ /** Structural equality over a diagnostic's salient fields (via the dedup key). */
65
+ export declare function areDiagnosticsEqual(a: Diagnostic, b: Diagnostic): boolean;
66
+ /** Options for {@link deduplicateAndCap}. */
67
+ export interface DedupOptions {
68
+ readonly maxPerFile?: number;
69
+ readonly maxTotal?: number;
70
+ }
71
+ /**
72
+ * Filter, deduplicate, severity-sort, and volume-cap a batch of diagnostic
73
+ * files against a cross-turn `delivered` ledger.
74
+ *
75
+ * - drops any diagnostic whose key is already in `delivered[uri]` (already
76
+ * surfaced in a prior turn — don't nag);
77
+ * - dedups within a file (identical keys collapse to one);
78
+ * - sorts each file's diagnostics by severity then position;
79
+ * - caps to `maxPerFile` per file and `maxTotal` overall (errors win the cap);
80
+ * - records every diagnostic it RETURNS into `delivered` (FIFO-capped per uri)
81
+ * so the next turn won't re-surface it.
82
+ *
83
+ * `delivered` is mutated in place. Files that end up empty are dropped.
84
+ */
85
+ export declare function deduplicateAndCap(files: readonly DiagnosticFile[], delivered: Map<string, Set<string>>, opts?: DedupOptions): DiagnosticFile[];
86
+ /** Inline ASCII severity marker (no `figures` dep). */
87
+ export declare function getSeveritySymbol(severity: DiagnosticSeverity): string;
88
+ /**
89
+ * Render a deduped/capped batch into a compact, agent-readable block. Positions
90
+ * are re-converted to 1-based for display (the engine carries 0-based). The
91
+ * output is truncated to `maxChars` so it can never blow up a context window.
92
+ */
93
+ export declare function formatDiagnosticsSummary(files: readonly DiagnosticFile[], maxChars?: number): string;
94
+ /** A diagnostics source: produces files for the workspace, honoring `signal`. */
95
+ export type DiagnosticRunner = (cwd: string, signal?: AbortSignal) => Promise<DiagnosticFile[]>;
96
+ /**
97
+ * Run a project `tsc --noEmit --pretty false` and parse the flat diagnostic
98
+ * lines. Resolves to `[]` on ANY failure (binary missing, abort, non-Node).
99
+ * The local `node_modules/.bin/tsc` is preferred over a global/`npx` install to
100
+ * avoid a version mismatch with the project's own config.
101
+ *
102
+ * tsc lines look like: `src/x.ts(12,5): error TS2322: Type 'a' ...`
103
+ * Positions are 1-based in tsc; we store them 0-based (subtract 1).
104
+ */
105
+ export declare function runTscDiagnostics(cwd: string, signal?: AbortSignal): Promise<DiagnosticFile[]>;
106
+ /** Parse the combined stdout/stderr of a `tsc --pretty false` run. */
107
+ export declare function parseTscOutput(output: string, cwd: string): DiagnosticFile[];
108
+ /**
109
+ * Run a project `eslint --format json` over `files` and parse the result.
110
+ * Resolves to `[]` when eslint is absent, no config is found, or anything else
111
+ * goes wrong. severity 2→Error, 1→Warning. Positions are 1-based; stored 0-based.
112
+ */
113
+ export declare function runEslintDiagnostics(files: readonly string[], cwd: string, signal?: AbortSignal): Promise<DiagnosticFile[]>;
114
+ /** Parse `eslint --format json` output. Resolves config-not-found to `[]`. */
115
+ export declare function parseEslintOutput(output: string, cwd: string): DiagnosticFile[];
116
+ /** Configuration for the {@link DiagnosticsEngine}. */
117
+ export interface DiagnosticsConfig {
118
+ /** Master switch. When `false` the engine reports nothing (opt-out). */
119
+ readonly enabled?: boolean;
120
+ /** The diagnostic sources to run. Defaults to `[runTscDiagnostics]`. */
121
+ readonly runners?: DiagnosticRunner[];
122
+ /** Per-run timeout in ms before the checker is aborted (default 30000). */
123
+ readonly timeoutMs?: number;
124
+ /** Per-file cap (default {@link MAX_PER_FILE}). */
125
+ readonly maxPerFile?: number;
126
+ /** Total cap (default {@link MAX_TOTAL}). */
127
+ readonly maxTotal?: number;
128
+ }
129
+ /**
130
+ * The post-edit diagnostics engine: runs checkers, diffs against a per-session
131
+ * baseline, and dedups across turns. Stateful and per-session — one instance
132
+ * lives on the conductor for the life of a session.
133
+ */
134
+ export declare class DiagnosticsEngine {
135
+ #private;
136
+ constructor(cwd: string, config?: DiagnosticsConfig);
137
+ /** Whether this engine is active. */
138
+ get enabled(): boolean;
139
+ /** Whether the per-session baseline has already been captured. */
140
+ get hasBaseline(): boolean;
141
+ /**
142
+ * Capture the per-session baseline NOW (before any edits land) by running the
143
+ * checkers once over the current project state. Every diagnostic present is
144
+ * recorded as pre-existing, so a later {@link newDiagnostics} surfaces only the
145
+ * problems the session's own edits introduced. Idempotent: a second call is a
146
+ * no-op once a baseline exists. Best-effort — never throws.
147
+ *
148
+ * Prefer calling this at session start (before the agent can edit anything); if
149
+ * it is never called, {@link newDiagnostics} falls back to seeding the baseline
150
+ * on its first invocation.
151
+ */
152
+ seedBaseline(signal?: AbortSignal): Promise<void>;
153
+ /**
154
+ * Run every checker, restrict the results to `editedPaths`, subtract the
155
+ * per-session baseline, dedup against prior turns, and volume-cap. Returns the
156
+ * NEW diagnostics for the edited files (empty when nothing new). Best-effort:
157
+ * never throws, never blocks beyond the configured timeout.
158
+ *
159
+ * The first invocation seeds the baseline from the current project state and
160
+ * returns `[]` (the pre-existing problems are the baseline, not "new").
161
+ */
162
+ newDiagnostics(editedPaths: readonly string[], signal?: AbortSignal): Promise<DiagnosticFile[]>;
163
+ /**
164
+ * Forget the delivered-key history for one uri so a problem at that file can
165
+ * re-surface (e.g. the agent edited it again). The baseline is left intact.
166
+ */
167
+ clearDeliveredForFile(uri: string): void;
168
+ /** Drop all cross-turn state and the baseline (re-seeds on the next run). */
169
+ reset(): void;
170
+ }
171
+ /** Whether a path looks like a TypeScript/JS source file (by extension). */
172
+ export declare function isTypeScriptOrJs(filePath: string): boolean;
173
+ /**
174
+ * Whether the workspace has a `tsconfig.json` at its root — the signal used to
175
+ * default the engine ON. Best-effort + synchronous: a probe failure (or a
176
+ * non-Node context) reports `false` so the engine defaults OFF and stays inert.
177
+ */
178
+ export declare function hasTsConfig(cwd: string): boolean;
179
+ /**
180
+ * Whether the workspace has an eslint config at its root — the signal used to
181
+ * additionally enable the eslint runner. Best-effort + synchronous.
182
+ */
183
+ export declare function hasEslintConfig(cwd: string): boolean;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Unit tests for the LSP-free post-edit diagnostics engine: the pure keying /
3
+ * dedup / cap / render logic, the tsc + eslint output parsers, and the
4
+ * baseline-diff behavior of {@link DiagnosticsEngine}.
5
+ *
6
+ * No network and no real `tsc`/`eslint` spawn — the engine is driven with
7
+ * scripted in-memory runners so the baseline/diff/dedup logic is exercised
8
+ * deterministically.
9
+ */
10
+ export {};
@@ -14,10 +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, ThinkingLevel, Model, Usage, KnownProvider, } 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, AgentMessage, AgentTool, CanUseToolFn, ThinkingLevel, Model, Usage, KnownProvider, PermissionMode, } from "./contract";
18
18
  export { conductorFault, TRANSCRIPT_SCHEMA } from "./contract";
19
19
  export { createSessionConductor, reduceState, noopCondense, 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";
25
+ export { parseBashCommand, catastrophicReason, isCatastrophicCommand, evaluateCatastrophic, bashSubcommandSubjects, type ParsedCommand, } from "./bash-guard";
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";
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Permission gate — integration against the REAL wired framework loop.
3
+ *
4
+ * The point of this suite (vs the pure {@link "./permissions.test"} unit tests) is
5
+ * to prove the gate actually lands where tools execute. It drives a real
6
+ * `indusagi/agent` `Agent` — the exact class the conductor's `makeAgent` wires —
7
+ * with a scripted `streamFn` that has the model request a tool call, and the
8
+ * SAME `createPermissionGate` output the conductor threads in as `canUseTool`.
9
+ *
10
+ * We assert through the framework's own message list (not a mock of an unwired
11
+ * module):
12
+ * - a **deny** rule blocks the tool: it never executes, and the turn carries an
13
+ * `isError` tool result with the deny message (an unanswered tool_use would
14
+ * 400 the next call, so the loop must still emit the result);
15
+ * - an **allow** rule (or read-only auto-allow) lets the tool execute;
16
+ * - **acceptEdits** auto-allows an edit without a rule;
17
+ * - **bypass** allows everything;
18
+ * - **plan** denies a mutating tool;
19
+ * - an **ask** with no host resolver denies (non-interactive default);
20
+ * - a live **mode switch** retargets subsequent calls with no agent rebuild.
21
+ */
22
+ export {};
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Conductor permission wiring — proves `createSessionConductor` threads the gate
3
+ * factory and exposes live `permissionMode()` / `setPermissionMode()`.
4
+ *
5
+ * The end-to-end "gate blocks a real tool" proof lives in
6
+ * {@link "./permission-gate.integration.test"} (it drives the real framework
7
+ * loop). Here we pin the PRODUCT seam: the conductor (a) seeds the mode from
8
+ * options, (b) updates it through `setPermissionMode`, and (c) hands the gate
9
+ * factory a getter that reads the conductor's LIVE mode — so a mode switch after
10
+ * assembly is visible to the gate with no agent rebuild. A scripted fake
11
+ * `AgentLike` keeps it network/disk-free, and a separate check confirms the gate
12
+ * factory IS resolved with a live-mode getter on the real-agent path.
13
+ */
14
+ export {};