open-multi-agent-kit 0.78.0 → 0.78.2

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 (132) hide show
  1. package/CHANGELOG.md +56 -15
  2. package/MATURITY.md +6 -2
  3. package/README.md +125 -26
  4. package/ROADMAP.md +36 -28
  5. package/dist/cli/register-basic-commands.js +3 -2
  6. package/dist/cli/register-mcp-dag-cron-screenshot-commands.js +2 -0
  7. package/dist/cli/register-spec-agent-goal-commands.js +45 -0
  8. package/dist/cli/register-tool-commands.js +11 -0
  9. package/dist/cli/register-workflow-commands.js +1 -0
  10. package/dist/cli/registry/tooling.js +3 -2
  11. package/dist/cli/release-promotion-gate.d.ts +14 -0
  12. package/dist/cli/release-promotion-gate.js +71 -0
  13. package/dist/cli/v2/release-commands.d.ts +29 -0
  14. package/dist/cli/v2/release-commands.js +95 -0
  15. package/dist/commands/chat/core.js +5 -0
  16. package/dist/commands/chat/native-root-loop.js +74 -1
  17. package/dist/commands/chat/slash/commands/session.js +19 -1
  18. package/dist/commands/dag-from-spec.d.ts +1 -0
  19. package/dist/commands/dag-from-spec.js +61 -1
  20. package/dist/commands/goal-interview.d.ts +18 -0
  21. package/dist/commands/goal-interview.js +396 -0
  22. package/dist/commands/graph.d.ts +62 -0
  23. package/dist/commands/graph.js +182 -0
  24. package/dist/commands/merge.d.ts +1 -0
  25. package/dist/commands/merge.js +88 -0
  26. package/dist/commands/parallel/core.js +3 -3
  27. package/dist/commands/provider.js +5 -3
  28. package/dist/commands/star.js +6 -1
  29. package/dist/commands/summary.d.ts +4 -1
  30. package/dist/commands/summary.js +103 -1
  31. package/dist/commands/team.d.ts +1 -0
  32. package/dist/commands/team.js +38 -0
  33. package/dist/contracts/interview.d.ts +106 -0
  34. package/dist/contracts/interview.js +9 -0
  35. package/dist/contracts/provider-health.d.ts +42 -0
  36. package/dist/contracts/provider-health.js +9 -0
  37. package/dist/evidence/index.d.ts +4 -0
  38. package/dist/evidence/index.js +2 -0
  39. package/dist/evidence/proof-trust-cli.d.ts +8 -0
  40. package/dist/evidence/proof-trust-cli.js +27 -0
  41. package/dist/evidence/proof-trust.d.ts +14 -0
  42. package/dist/evidence/proof-trust.js +381 -0
  43. package/dist/evidence/regression-proof-matrix.d.ts +42 -0
  44. package/dist/evidence/regression-proof-matrix.js +72 -0
  45. package/dist/goal/intent-frame.d.ts +30 -0
  46. package/dist/goal/intent-frame.js +39 -9
  47. package/dist/goal/interview-assimilation.d.ts +13 -0
  48. package/dist/goal/interview-assimilation.js +383 -0
  49. package/dist/goal/interview-question-bank.d.ts +11 -0
  50. package/dist/goal/interview-question-bank.js +225 -0
  51. package/dist/goal/interview-scoring.d.ts +31 -0
  52. package/dist/goal/interview-scoring.js +187 -0
  53. package/dist/goal/interview-session.d.ts +25 -0
  54. package/dist/goal/interview-session.js +116 -0
  55. package/dist/input/input-envelope.d.ts +22 -0
  56. package/dist/input/input-envelope.js +1 -0
  57. package/dist/memory/local-graph-memory-store.d.ts +15 -0
  58. package/dist/memory/local-graph-memory-store.js +176 -0
  59. package/dist/memory/memory-store.d.ts +18 -0
  60. package/dist/memory/memory-store.js +18 -0
  61. package/dist/orchestration/adaptorch-topology.d.ts +59 -0
  62. package/dist/orchestration/adaptorch-topology.js +194 -0
  63. package/dist/orchestration/capability-routing.d.ts +23 -0
  64. package/dist/orchestration/capability-routing.js +56 -0
  65. package/dist/orchestration/dag-compiler-types.d.ts +3 -0
  66. package/dist/orchestration/dag-compiler.js +14 -1
  67. package/dist/orchestration/parallel-orchestrator.d.ts +6 -0
  68. package/dist/orchestration/parallel-orchestrator.js +31 -0
  69. package/dist/providers/provider-health.d.ts +39 -0
  70. package/dist/providers/provider-health.js +161 -0
  71. package/dist/runtime/advanced-control-loop.d.ts +60 -0
  72. package/dist/runtime/advanced-control-loop.js +136 -0
  73. package/dist/runtime/agent-runtime.d.ts +10 -0
  74. package/dist/runtime/blast-radius.d.ts +10 -0
  75. package/dist/runtime/blast-radius.js +14 -0
  76. package/dist/runtime/context-broker.d.ts +13 -4
  77. package/dist/runtime/context-broker.js +14 -1
  78. package/dist/runtime/contracts/evidence.d.ts +87 -0
  79. package/dist/runtime/contracts/evidence.js +7 -0
  80. package/dist/runtime/contracts/router-v2.d.ts +44 -0
  81. package/dist/runtime/contracts/router-v2.js +4 -0
  82. package/dist/runtime/contracts/weakness-remediation.d.ts +67 -0
  83. package/dist/runtime/contracts/weakness-remediation.js +36 -0
  84. package/dist/runtime/headroom-policy.d.ts +37 -0
  85. package/dist/runtime/headroom-policy.js +122 -0
  86. package/dist/runtime/kimi-api-runtime.js +59 -1
  87. package/dist/runtime/ouroboros-policy.d.ts +57 -0
  88. package/dist/runtime/ouroboros-policy.js +134 -0
  89. package/dist/runtime/proof-bundle-trust.d.ts +74 -0
  90. package/dist/runtime/proof-bundle-trust.js +100 -0
  91. package/dist/runtime/provider-maturity-gate.d.ts +41 -0
  92. package/dist/runtime/provider-maturity-gate.js +101 -0
  93. package/dist/runtime/public-surface.d.ts +93 -0
  94. package/dist/runtime/public-surface.js +146 -0
  95. package/dist/runtime/router-v2-scoring.d.ts +11 -0
  96. package/dist/runtime/router-v2-scoring.js +151 -0
  97. package/dist/runtime/runtime-backed-task-runner.js +9 -1
  98. package/dist/runtime/tool-dispatch-contracts.d.ts +57 -1
  99. package/dist/runtime/tool-dispatch-contracts.js +79 -3
  100. package/dist/runtime/weakness-remediation-index.d.ts +27 -0
  101. package/dist/runtime/weakness-remediation-index.js +37 -0
  102. package/dist/safety/tool-authority-gate.d.ts +62 -0
  103. package/dist/safety/tool-authority-gate.js +108 -0
  104. package/dist/schema/proof-bundle.schema.d.ts +26 -26
  105. package/dist/schema/provider.schema.d.ts +4 -4
  106. package/dist/util/clipboard-image.d.ts +49 -0
  107. package/dist/util/clipboard-image.js +263 -0
  108. package/dist/util/first-run-star.d.ts +9 -0
  109. package/dist/util/first-run-star.js +42 -1
  110. package/dist/util/terminal-input.d.ts +20 -0
  111. package/dist/util/terminal-input.js +32 -0
  112. package/dist/util/update-check.d.ts +6 -1
  113. package/dist/util/update-check.js +35 -1
  114. package/docs/2026-06-08/critical-issues.md +20 -0
  115. package/docs/2026-06-08/improvements.md +14 -0
  116. package/docs/2026-06-08/init-checklist.md +25 -0
  117. package/docs/2026-06-08/plan.md +20 -0
  118. package/docs/2026-06-09/critical-issues.md +20 -0
  119. package/docs/2026-06-09/improvements.md +14 -0
  120. package/docs/2026-06-09/init-checklist.md +25 -0
  121. package/docs/2026-06-09/plan.md +20 -0
  122. package/docs/getting-started.md +31 -3
  123. package/docs/github-organic-promotion.md +127 -0
  124. package/docs/integrations/ouroboros.md +96 -0
  125. package/docs/native-root-runtime-algorithms.md +301 -0
  126. package/docs/provider-maturity.md +1 -1
  127. package/docs/versioning.md +3 -3
  128. package/package.json +4 -3
  129. package/readmeasset/ASSET_INDEX.md +1 -0
  130. package/templates/skills/agents/omk-agent-reach-websearch/SKILL.md +55 -0
  131. package/templates/skills/kimi/omk-agent-reach-websearch/SKILL.md +55 -0
  132. package/dist/native/linux-x64/omk-safety +0 -0
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Phase 5 — Release Promotion Gate (Algorithm 8)
3
+ *
4
+ * Computes a release viability score R_v from ten normalized input
5
+ * dimensions and derives a verdict: block, pre-release, or stable.
6
+ */
7
+ import { type ReleasePromotionInputs, type ReleasePromotionResult } from "../runtime/contracts/weakness-remediation.js";
8
+ /** Gate engine contract. */
9
+ export interface ReleasePromotionGate {
10
+ /** Evaluate inputs and return scored result with verdict. */
11
+ evaluate(inputs: ReleasePromotionInputs): ReleasePromotionResult;
12
+ }
13
+ /** Factory that creates the default release promotion gate. */
14
+ export declare function createReleasePromotionGate(): ReleasePromotionGate;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Phase 5 — Release Promotion Gate (Algorithm 8)
3
+ *
4
+ * Computes a release viability score R_v from ten normalized input
5
+ * dimensions and derives a verdict: block, pre-release, or stable.
6
+ */
7
+ import { RELEASE_GATE_WEIGHTS, } from "../runtime/contracts/weakness-remediation.js";
8
+ /** Factory that creates the default release promotion gate. */
9
+ export function createReleasePromotionGate() {
10
+ return {
11
+ evaluate(inputs) {
12
+ const w = RELEASE_GATE_WEIGHTS;
13
+ const demoRun = inputs.demoRun ?? false;
14
+ const maturity = inputs.maturity ?? inputs.providerMinimum ?? 0;
15
+ const rawScore = w.ci * inputs.ci +
16
+ w.build * (inputs.build ?? 0) +
17
+ w.types * (inputs.types ?? 0) +
18
+ w.tests * (inputs.tests ?? 0) +
19
+ w.install * inputs.freshInstallSmoke +
20
+ w.demo * (demoRun ? 1 : 0) +
21
+ w.proof * inputs.proofMedian +
22
+ w.maturity * maturity +
23
+ w.docs * inputs.docs -
24
+ w.regression * inputs.regressionSeverity;
25
+ const score = clamp01(rawScore);
26
+ const reasons = [];
27
+ const blocked = inputs.ci === 0 || inputs.freshInstallSmoke === 0 || !demoRun;
28
+ if (blocked) {
29
+ if (inputs.ci === 0) {
30
+ reasons.push("CI score is 0 (blocking)");
31
+ }
32
+ if (inputs.freshInstallSmoke === 0) {
33
+ reasons.push("Fresh install smoke is 0 (blocking)");
34
+ }
35
+ if (!demoRun) {
36
+ reasons.push("Minimal verified demo run failed or missing (blocking)");
37
+ }
38
+ }
39
+ let verdict;
40
+ if (blocked) {
41
+ verdict = "block";
42
+ }
43
+ else if (score >= 0.90 && inputs.proofMedian >= 0.85 && maturity >= 0.80) {
44
+ verdict = "stable";
45
+ reasons.push(`Score ${formatScore(score)} meets stable threshold (≥0.90) with proof≥0.85 and maturity≥0.80`);
46
+ }
47
+ else if (score >= 0.75 && inputs.proofMedian >= 0.75) {
48
+ verdict = "pre-release";
49
+ reasons.push(`Score ${formatScore(score)} meets pre-release threshold (≥0.75) with proof≥0.75`);
50
+ }
51
+ else {
52
+ verdict = "block";
53
+ reasons.push(`Score ${formatScore(score)} below pre-release threshold (≥0.75) or proof below 0.75`);
54
+ }
55
+ return {
56
+ score,
57
+ verdict,
58
+ blocked,
59
+ reasons,
60
+ };
61
+ },
62
+ };
63
+ }
64
+ function clamp01(n) {
65
+ if (Number.isNaN(n))
66
+ return 0;
67
+ return Math.max(0, Math.min(1, n));
68
+ }
69
+ function formatScore(n) {
70
+ return n.toFixed(4);
71
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Section 21 — CLI v2 Release Commands (Clipanion)
3
+ *
4
+ * `omk release check` — Evaluate release promotion gate
5
+ * `omk release promote` — Promote release if gate passes (stub)
6
+ */
7
+ import { Command, type Cli } from "clipanion";
8
+ type ClipanionRegistrar = Pick<Cli, "register">;
9
+ export declare class ReleaseCheckCommand extends Command {
10
+ static paths: string[][];
11
+ static usage: import("clipanion").Usage;
12
+ ci: string;
13
+ schema: string;
14
+ docs: string;
15
+ proof: string;
16
+ provider: string;
17
+ regression: string;
18
+ install: string;
19
+ semver: string;
20
+ json: boolean;
21
+ execute(): Promise<number>;
22
+ }
23
+ export declare class ReleasePromoteCommand extends Command {
24
+ static paths: string[][];
25
+ static usage: import("clipanion").Usage;
26
+ execute(): Promise<number>;
27
+ }
28
+ export declare function registerReleaseCommandsV2(cli: ClipanionRegistrar): void;
29
+ export {};
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Section 21 — CLI v2 Release Commands (Clipanion)
3
+ *
4
+ * `omk release check` — Evaluate release promotion gate
5
+ * `omk release promote` — Promote release if gate passes (stub)
6
+ */
7
+ import { Command, Option } from "clipanion";
8
+ import { createReleasePromotionGate } from "../release-promotion-gate.js";
9
+ // ──────────────────────────────────────────────
10
+ // Release Check
11
+ // ──────────────────────────────────────────────
12
+ export class ReleaseCheckCommand extends Command {
13
+ static paths = [["release", "check"]];
14
+ static usage = Command.Usage({
15
+ description: "Evaluate release promotion gate",
16
+ examples: [["Check release readiness", "omk release check"]],
17
+ });
18
+ ci = Option.String("--ci", "1", { description: "CI score (0–1)" });
19
+ schema = Option.String("--schema", "1", { description: "Schema score (0–1)" });
20
+ docs = Option.String("--docs", "1", { description: "Docs score (0–1)" });
21
+ proof = Option.String("--proof", "1", { description: "Proof median (0–1)" });
22
+ provider = Option.String("--provider", "1", { description: "Provider minimum (0–1)" });
23
+ regression = Option.String("--regression", "0", { description: "Regression severity (0–1)" });
24
+ install = Option.String("--install", "1", { description: "Fresh install smoke (0–1)" });
25
+ semver = Option.String("--semver", "1", { description: "Semver score (0–1)" });
26
+ json = Option.Boolean("--json", false, { description: "JSON output" });
27
+ async execute() {
28
+ const gate = createReleasePromotionGate();
29
+ const inputs = {
30
+ ci: Number.parseFloat(this.ci),
31
+ schema: Number.parseFloat(this.schema),
32
+ docs: Number.parseFloat(this.docs),
33
+ proofMedian: Number.parseFloat(this.proof),
34
+ providerMinimum: Number.parseFloat(this.provider),
35
+ regressionSeverity: Number.parseFloat(this.regression),
36
+ freshInstallSmoke: Number.parseFloat(this.install),
37
+ semver: Number.parseFloat(this.semver),
38
+ };
39
+ const result = gate.evaluate(inputs);
40
+ if (this.json) {
41
+ this.context.stdout.write(JSON.stringify(result, null, 2) + "\n");
42
+ }
43
+ else {
44
+ this.context.stdout.write("Release Promotion Gate\n");
45
+ this.context.stdout.write(`Score: ${result.score.toFixed(4)}\n`);
46
+ this.context.stdout.write(`Verdict: ${result.verdict}\n`);
47
+ this.context.stdout.write(`Blocked: ${result.blocked}\n`);
48
+ if (result.reasons.length > 0) {
49
+ this.context.stdout.write("Reasons:\n");
50
+ for (const reason of result.reasons) {
51
+ this.context.stdout.write(` - ${reason}\n`);
52
+ }
53
+ }
54
+ }
55
+ return result.verdict === "block" ? 1 : 0;
56
+ }
57
+ }
58
+ // ──────────────────────────────────────────────
59
+ // Release Promote
60
+ // ──────────────────────────────────────────────
61
+ export class ReleasePromoteCommand extends Command {
62
+ static paths = [["release", "promote"]];
63
+ static usage = Command.Usage({
64
+ description: "Promote release if gate passes",
65
+ examples: [["Promote release", "omk release promote"]],
66
+ });
67
+ async execute() {
68
+ const gate = createReleasePromotionGate();
69
+ const inputs = {
70
+ ci: Number.parseFloat(process.env.OMK_RELEASE_CI ?? "1"),
71
+ schema: Number.parseFloat(process.env.OMK_RELEASE_SCHEMA ?? "1"),
72
+ docs: Number.parseFloat(process.env.OMK_RELEASE_DOCS ?? "1"),
73
+ proofMedian: Number.parseFloat(process.env.OMK_RELEASE_PROOF ?? "1"),
74
+ providerMinimum: Number.parseFloat(process.env.OMK_RELEASE_PROVIDER ?? "1"),
75
+ regressionSeverity: Number.parseFloat(process.env.OMK_RELEASE_REGRESSION ?? "0"),
76
+ freshInstallSmoke: Number.parseFloat(process.env.OMK_RELEASE_INSTALL ?? "1"),
77
+ semver: Number.parseFloat(process.env.OMK_RELEASE_SEMVER ?? "1"),
78
+ };
79
+ const result = gate.evaluate(inputs);
80
+ this.context.stdout.write(JSON.stringify(result, null, 2) + "\n");
81
+ if (result.verdict === "block") {
82
+ this.context.stderr.write("Release promotion blocked.\n");
83
+ return 1;
84
+ }
85
+ this.context.stdout.write("Release promotion approved.\n");
86
+ return 0;
87
+ }
88
+ }
89
+ // ──────────────────────────────────────────────
90
+ // Registration
91
+ // ──────────────────────────────────────────────
92
+ export function registerReleaseCommandsV2(cli) {
93
+ cli.register(ReleaseCheckCommand);
94
+ cli.register(ReleasePromoteCommand);
95
+ }
@@ -245,6 +245,11 @@ export async function chatCommand(options) {
245
245
  catch {
246
246
  // Update prompts are advisory and must not block chat startup.
247
247
  }
248
+ // NOTE: the star/update prompts above use @inquirer/prompts (raw mode + their
249
+ // own readline) and can leave interactive stdin paused. The native root loop
250
+ // re-validates and resumes a paused TTY via resumeInteractiveInput() right
251
+ // before it builds its readline (see runNativeOmkRootLoop), so the first run
252
+ // after `omk init` does not exit immediately with "Session ended".
248
253
  }
249
254
  // ── tmux cockpit layout: keep prompt pane clean; move cockpit/HUD telemetry to the side pane ──
250
255
  const explicitTmux = layout === "tmux";
@@ -2,6 +2,9 @@ import { style } from "../../util/theme.js";
2
2
  import { runShell } from "../../util/shell.js";
3
3
  import { createDag } from "../../orchestration/dag.js";
4
4
  import { applyCapabilityInjectionToRouting, buildCapabilityInjection, } from "../../runtime/capability-injection.js";
5
+ import { capabilityScopesFromRouting } from "../../orchestration/capability-routing.js";
6
+ import { decideToolAuthority, } from "../../safety/tool-authority-gate.js";
7
+ import { resolveToolAuthorityEnforcement } from "../../runtime/tool-dispatch-contracts.js";
5
8
  import { compileBloatToNlp, } from "../../runtime/debloat-nlp.js";
6
9
  import { buildPromptEnvelope, renderPromptEnvelope, } from "../../runtime/prompt-envelope.js";
7
10
  import { buildTaskRunContext } from "../../runtime/worker-manifest.js";
@@ -10,6 +13,7 @@ import { persistInputEnvelope } from "../../input/input-artifacts.js";
10
13
  import { buildDagCompileResult } from "../../orchestration/dag-compiler.js";
11
14
  import { persistDagCompileArtifacts } from "../../orchestration/dag-artifacts.js";
12
15
  import { TerminalOwner } from "../../util/terminal-owner.js";
16
+ import { resumeInteractiveInput } from "../../util/terminal-input.js";
13
17
  import { executeHarnessRun } from "../../harness/execute-harness-run.js";
14
18
  import { normalizeProviderId, readProviderRegistry } from "../../providers/model-registry.js";
15
19
  import { renderProviderModelTable } from "../../providers/model-table.js";
@@ -192,6 +196,53 @@ function bootstrapProviderPolicy(bootstrap) {
192
196
  return "auto";
193
197
  }
194
198
  }
199
+ function nativeTurnRiskToToolOp(risk) {
200
+ switch (risk) {
201
+ case "merge":
202
+ return "merge";
203
+ case "shell":
204
+ return "shell";
205
+ case "write":
206
+ return "write";
207
+ default:
208
+ return "read";
209
+ }
210
+ }
211
+ /**
212
+ * Shadow-only tool-authority observability at the live turn dispatch
213
+ * checkpoint. The kimi runner executes tools inside a spawned CLI, so per-tool
214
+ * enforcement lives in dispatchToolCallsByContract; here we only compute and
215
+ * record the turn-level verdict for the trace. Default output is byte-identical
216
+ * (emitted only under OMK_DEBUG / OMK_TOOL_AUTHORITY_TRACE); this path never
217
+ * blocks dispatch.
218
+ */
219
+ function recordNativeTurnToolAuthority(input) {
220
+ const traceEnabled = input.env.OMK_DEBUG === "1" ||
221
+ /^(1|true|yes|on)$/i.test(input.env.OMK_TOOL_AUTHORITY_TRACE ?? "");
222
+ if (!traceEnabled)
223
+ return;
224
+ const routing = input.node.routing;
225
+ const scopes = capabilityScopesFromRouting(routing);
226
+ const op = nativeTurnRiskToToolOp(routing?.risk);
227
+ const enforce = resolveToolAuthorityEnforcement(input.env);
228
+ const decision = decideToolAuthority({
229
+ op,
230
+ writeAuthority: scopes.writeAuthority,
231
+ shellAuthority: scopes.shellAuthority,
232
+ approvalPolicy: nativeApprovalPolicy(routing?.approvalPolicy ?? routing?.executionPrompt),
233
+ sandboxMode: routing?.sandboxMode === "read-only" ? "read-only" : "workspace-write",
234
+ tty: Boolean(process.stdout.isTTY),
235
+ });
236
+ const line = ` tool-authority(${enforce ? "enforce" : "shadow"}): node=${input.node.id} ` +
237
+ `op=${op} decision=${decision} write=${scopes.writeAuthority} ` +
238
+ `shell=${scopes.shellAuthority} sandbox=${routing?.sandboxMode ?? "auto"}\n`;
239
+ if (input.renderer) {
240
+ input.renderer.emit({ type: "control:output", text: style.phosphorDim(line) });
241
+ }
242
+ else {
243
+ process.stderr.write(style.phosphorDim(line));
244
+ }
245
+ }
195
246
  function emitNativeTurnRoute(input) {
196
247
  if (!input.heartbeatEnabled)
197
248
  return;
@@ -463,6 +514,7 @@ async function executeNativeRootTurn(input) {
463
514
  const routing = input.node.routing;
464
515
  const activity = describeNativeTurnActivity(input.node);
465
516
  emitNativeTurnRoute(input);
517
+ recordNativeTurnToolAuthority({ node: input.node, env: input.env, renderer: input.renderer });
466
518
  let heartbeatPrinted = false;
467
519
  let heartbeatLineClosed = false;
468
520
  const heartbeat = input.heartbeatEnabled
@@ -598,6 +650,7 @@ async function executeNativeRootHarnessTurn(input) {
598
650
  skillNames: node.routing?.skills ?? input.skillNames,
599
651
  hookNames: node.routing?.hooks ?? input.hookNames,
600
652
  });
653
+ recordNativeTurnToolAuthority({ node, env: input.env, renderer: input.renderer });
601
654
  },
602
655
  onNodeComplete: (node, result) => {
603
656
  completed.push({ node, result });
@@ -666,6 +719,13 @@ export async function runNativeOmkRootLoop(input) {
666
719
  else
667
720
  process.stdout.write(`${text}\n`);
668
721
  };
722
+ // Defensive stdin re-validation before the chat readline takes ownership.
723
+ // The first-run GitHub-star / update prompts (@inquirer/prompts) can take
724
+ // over raw mode and leave the shared interactive stdin paused; a paused TTY
725
+ // makes the readline below see an immediate EOF/'close' and exit the loop
726
+ // with "Session ended". This only resumes an explicitly-paused TTY and never
727
+ // touches non-TTY stdin (its EOF/exit behavior must stay unchanged).
728
+ resumeInteractiveInput(process.stdin);
669
729
  const { createInterface } = await import("readline");
670
730
  const rl = createInterface({
671
731
  input: process.stdin,
@@ -731,6 +791,7 @@ export async function runNativeOmkRootLoop(input) {
731
791
  let running = true;
732
792
  let readlineClosed = false;
733
793
  let activeTurnAbort;
794
+ let pendingPastedImageLine;
734
795
  const queuedLines = [];
735
796
  let pendingLineResolve;
736
797
  const resolveNextLine = (value) => {
@@ -786,7 +847,7 @@ export async function runNativeOmkRootLoop(input) {
786
847
  const userInput = await readPromptLine();
787
848
  if (userInput === undefined)
788
849
  break;
789
- const line = userInput.trim();
850
+ let line = userInput.trim();
790
851
  if (!line)
791
852
  continue;
792
853
  renderer?.emit({ type: "input:submitted", text: line });
@@ -795,6 +856,10 @@ export async function runNativeOmkRootLoop(input) {
795
856
  break;
796
857
  }
797
858
  const parsedSlash = parseSlashInput(line);
859
+ if (!parsedSlash && pendingPastedImageLine) {
860
+ line = `${pendingPastedImageLine}\n${line}`;
861
+ pendingPastedImageLine = undefined;
862
+ }
798
863
  const inputEnvelope = buildNativeInputEnvelope({
799
864
  loopInput: input,
800
865
  state,
@@ -836,6 +901,14 @@ export async function runNativeOmkRootLoop(input) {
836
901
  try {
837
902
  await terminalOwner.withChildProcess(rl, async () => {
838
903
  const result = await runSlashHandler(handler, parsedSlash, slashContext);
904
+ if (parsedSlash.command === "/paste" && result.ok) {
905
+ const pasteLine = result.text?.trim();
906
+ if (pasteLine) {
907
+ pendingPastedImageLine = pendingPastedImageLine
908
+ ? `${pendingPastedImageLine}\n${pasteLine}`
909
+ : pasteLine;
910
+ }
911
+ }
839
912
  if (result.exit)
840
913
  running = false;
841
914
  });
@@ -1,7 +1,7 @@
1
1
  import { style } from "../../../../util/theme.js";
2
2
  import { readTodos } from "../../../../util/todo-sync.js";
3
3
  import { commandLine, formatScopedNames, section } from "../format.js";
4
- import { okSlashResult } from "../result.js";
4
+ import { errorSlashResult, okSlashResult } from "../result.js";
5
5
  export function buildSessionSlashCommands() {
6
6
  return [
7
7
  {
@@ -22,6 +22,23 @@ export function buildSessionSlashCommands() {
22
22
  examples: ["/help"],
23
23
  handler: () => okSlashResult({ text: renderSlashHelp() }),
24
24
  },
25
+ {
26
+ name: "/paste",
27
+ aliases: [],
28
+ group: "session",
29
+ summary: "Paste image from clipboard",
30
+ usage: "/paste",
31
+ examples: ["/paste"],
32
+ handler: async (ctx) => {
33
+ const { pasteClipboardImage } = await import("../../../../util/clipboard-image.js");
34
+ const result = pasteClipboardImage(ctx.input.root);
35
+ if (!result.ok)
36
+ return errorSlashResult(result.error ?? "No image in clipboard");
37
+ if (!result.relativePath)
38
+ return errorSlashResult("Clipboard image saved without a relative path");
39
+ return okSlashResult({ text: `Image file: ${result.relativePath}` });
40
+ },
41
+ },
25
42
  {
26
43
  name: "/status",
27
44
  aliases: ["/s"],
@@ -76,6 +93,7 @@ function renderSlashHelp() {
76
93
  commandLine("/theme", "<system24|green-rain|neon-grid|rust-forge|plain|high-contrast>", "Set session theme"),
77
94
  commandLine("/view", "<summary|graph|evidence|tool-plane|events>", "Set control-plane view"),
78
95
  commandLine("/animation", "<off|low|auto|full>", "Set animation policy"),
96
+ commandLine("/paste", "", "Paste image from clipboard"),
79
97
  commandLine("/status", "", "Session status"),
80
98
  commandLine("/clear", "/cls", "Clear screen"),
81
99
  commandLine("/runs", "", "Recent run history"),
@@ -27,4 +27,5 @@ export declare function dagFromSpecCommand(specDir: string, options?: {
27
27
  parallel?: boolean;
28
28
  runId?: string;
29
29
  run?: string;
30
+ json?: boolean;
30
31
  }): Promise<Dag>;
@@ -3,7 +3,9 @@ import { join } from "path";
3
3
  import { writeFile, mkdir } from "fs/promises";
4
4
  import { getProjectRoot, pathExists, getRunPath, getRunsDir } from "../util/fs.js";
5
5
  import { header, label } from "../util/theme.js";
6
- import { createDag } from "../orchestration/dag.js";
6
+ import { createDag, getExecutionWaves } from "../orchestration/dag.js";
7
+ import { createOmkJsonEnvelope } from "../util/json-envelope.js";
8
+ import { emitJson } from "../util/cli-contract.js";
7
9
  function inferRole(description, explicitRole) {
8
10
  if (explicitRole)
9
11
  return explicitRole;
@@ -243,6 +245,61 @@ export async function loadSpecDag(specDir, options = {}) {
243
245
  }
244
246
  return tasksToDag(tasks, { parallel: options.parallel });
245
247
  }
248
+ /**
249
+ * Project the in-memory DAG artifact into the envelope `data` shape. The inner
250
+ * dag artifact (nodes) is preserved verbatim; edges/batches/stats are derived
251
+ * from the existing dependsOn graph (no new data sources).
252
+ */
253
+ function buildDagJsonData(inputId, dag) {
254
+ const edges = dag.nodes.flatMap((node) => node.dependsOn.map((from) => ({ from, to: node.id })));
255
+ const batches = getExecutionWaves(dag).map((wave) => wave.map((node) => node.id));
256
+ return {
257
+ inputId,
258
+ nodes: dag.nodes,
259
+ edges,
260
+ batches,
261
+ stats: { nodes: dag.nodes.length, edges: edges.length, batches: batches.length },
262
+ };
263
+ }
264
+ /**
265
+ * JSON path for `omk dag from-spec --json`.
266
+ * Emits EXACTLY ONE omk.contract.v1 envelope to stdout (no banner, no ANSI) and
267
+ * never throws on a missing/empty tasks.md, so JSON stdout stays one envelope.
268
+ */
269
+ async function emitDagFromSpecJson(targetDir, options) {
270
+ const started = Date.now();
271
+ let dag;
272
+ try {
273
+ dag = await loadSpecDag(targetDir, { parallel: options.parallel });
274
+ }
275
+ catch (err) {
276
+ const message = err instanceof Error ? err.message : String(err);
277
+ const empty = { nodes: [] };
278
+ emitJson(createOmkJsonEnvelope({
279
+ command: "dag",
280
+ status: "not-applicable",
281
+ ok: false,
282
+ data: buildDagJsonData(targetDir, empty),
283
+ warnings: [
284
+ { code: "RUN_ARTIFACT_MISSING", message, recoverable: true, severity: "warning" },
285
+ ],
286
+ durationMs: Date.now() - started,
287
+ }));
288
+ return empty;
289
+ }
290
+ if (options.output) {
291
+ await mkdir(getRunsDir(getProjectRoot()), { recursive: true });
292
+ await writeFile(options.output, JSON.stringify(dag, null, 2));
293
+ }
294
+ emitJson(createOmkJsonEnvelope({
295
+ command: "dag",
296
+ status: "passed",
297
+ ok: true,
298
+ data: buildDagJsonData(targetDir, dag),
299
+ durationMs: Date.now() - started,
300
+ }));
301
+ return dag;
302
+ }
246
303
  export async function dagFromSpecCommand(specDir, options = {}) {
247
304
  const root = getProjectRoot();
248
305
  let targetDir = specDir;
@@ -266,6 +323,9 @@ export async function dagFromSpecCommand(specDir, options = {}) {
266
323
  targetDir = getRunPath(options.run, undefined, root);
267
324
  }
268
325
  }
326
+ if (options.json === true || process.argv.includes("--json")) {
327
+ return emitDagFromSpecJson(targetDir, { output: options.output, parallel: options.parallel });
328
+ }
269
329
  const dag = await loadSpecDag(targetDir, { parallel: options.parallel });
270
330
  const dagJson = JSON.stringify(dag, null, 2);
271
331
  if (options.output) {
@@ -0,0 +1,18 @@
1
+ interface GoalInterviewOptions {
2
+ goalId?: string;
3
+ mode?: string;
4
+ depth?: string;
5
+ maxQuestions?: string;
6
+ answers?: string;
7
+ image?: string;
8
+ writeSpec?: boolean;
9
+ json?: boolean;
10
+ }
11
+ interface GoalRefineOptions {
12
+ fromInterview?: string;
13
+ plan?: boolean;
14
+ json?: boolean;
15
+ }
16
+ export declare function goalInterviewCommand(input: string | undefined, options: GoalInterviewOptions): Promise<void>;
17
+ export declare function goalRefineCommand(goalId: string, options: GoalRefineOptions): Promise<void>;
18
+ export {};