ai-runtime-engine 1.3.0 → 2.8.0

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 (153) hide show
  1. package/CHANGELOG.md +638 -0
  2. package/dist/agents/admit.d.ts +69 -0
  3. package/dist/agents/admit.js +129 -0
  4. package/dist/agents/definition.d.ts +36 -0
  5. package/dist/agents/definition.js +9 -0
  6. package/dist/agents/envelope.d.ts +53 -0
  7. package/dist/agents/envelope.js +68 -0
  8. package/dist/agents/finding.d.ts +79 -0
  9. package/dist/agents/finding.js +80 -0
  10. package/dist/agents/roles.d.ts +36 -0
  11. package/dist/agents/roles.js +44 -0
  12. package/dist/agents/synthesize.d.ts +44 -0
  13. package/dist/agents/synthesize.js +60 -0
  14. package/dist/agents/task.d.ts +112 -0
  15. package/dist/agents/task.js +48 -0
  16. package/dist/agents/worker.d.ts +91 -0
  17. package/dist/agents/worker.js +377 -0
  18. package/dist/capabilities/capability.d.ts +117 -0
  19. package/dist/capabilities/capability.js +66 -0
  20. package/dist/capabilities/registry.d.ts +139 -0
  21. package/dist/capabilities/registry.js +413 -0
  22. package/dist/capabilities/vocabulary.d.ts +32 -0
  23. package/dist/capabilities/vocabulary.js +34 -0
  24. package/dist/cli/cli.js +55 -4
  25. package/dist/cli/commands/cleanup.js +29 -27
  26. package/dist/cli/commands/doctor.d.ts +14 -0
  27. package/dist/cli/commands/doctor.js +38 -8
  28. package/dist/cli/commands/executions.js +34 -25
  29. package/dist/cli/commands/info.d.ts +1 -0
  30. package/dist/cli/commands/info.js +11 -9
  31. package/dist/cli/commands/init.js +19 -0
  32. package/dist/cli/commands/inspect.d.ts +40 -1
  33. package/dist/cli/commands/inspect.js +157 -2
  34. package/dist/cli/commands/mcp.d.ts +45 -0
  35. package/dist/cli/commands/mcp.js +148 -0
  36. package/dist/cli/commands/route.js +21 -0
  37. package/dist/cli/commands/run.d.ts +1 -0
  38. package/dist/cli/commands/run.js +21 -2
  39. package/dist/cli/commands/skills.d.ts +2 -0
  40. package/dist/cli/commands/skills.js +29 -7
  41. package/dist/cli/interactive/ansi.d.ts +41 -0
  42. package/dist/cli/interactive/ansi.js +43 -0
  43. package/dist/cli/interactive/complete.d.ts +10 -0
  44. package/dist/cli/interactive/complete.js +19 -0
  45. package/dist/cli/interactive/lanes.d.ts +69 -0
  46. package/dist/cli/interactive/lanes.js +181 -0
  47. package/dist/cli/interactive/repl.d.ts +3 -0
  48. package/dist/cli/interactive/repl.js +91 -13
  49. package/dist/cli/interactive/session.d.ts +8 -0
  50. package/dist/cli/interactive/session.js +73 -2
  51. package/dist/cli/render.d.ts +7 -0
  52. package/dist/cli/render.js +10 -0
  53. package/dist/cli/runtimeSession.d.ts +11 -0
  54. package/dist/cli/runtimeSession.js +17 -0
  55. package/dist/config/defaults.d.ts +3 -1
  56. package/dist/config/defaults.js +2 -0
  57. package/dist/config/schema.d.ts +1 -0
  58. package/dist/config/schema.js +2 -2
  59. package/dist/context/lossVerifier.d.ts +24 -0
  60. package/dist/context/lossVerifier.js +45 -0
  61. package/dist/context/summarize.d.ts +19 -0
  62. package/dist/context/summarize.js +53 -0
  63. package/dist/core/fallback/fallback.d.ts +5 -0
  64. package/dist/core/fallback/fallback.js +3 -1
  65. package/dist/core/router/router.d.ts +3 -0
  66. package/dist/core/router/router.js +1 -0
  67. package/dist/executions/agentTasks.d.ts +627 -0
  68. package/dist/executions/agentTasks.js +149 -0
  69. package/dist/executions/checkpoint.d.ts +5 -1
  70. package/dist/executions/checkpoint.js +13 -1
  71. package/dist/executions/execution.d.ts +36 -2
  72. package/dist/executions/store.d.ts +37 -0
  73. package/dist/executions/store.js +33 -0
  74. package/dist/generation/generateAdapter.d.ts +14 -0
  75. package/dist/generation/generateAdapter.js +38 -0
  76. package/dist/generation/generateSkill.d.ts +26 -0
  77. package/dist/generation/generateSkill.js +51 -0
  78. package/dist/index.d.ts +47 -4
  79. package/dist/index.js +33 -2
  80. package/dist/mcp/client.d.ts +70 -0
  81. package/dist/mcp/client.js +221 -0
  82. package/dist/mcp/manager.d.ts +151 -0
  83. package/dist/mcp/manager.js +493 -0
  84. package/dist/mcp/protocol.d.ts +216 -0
  85. package/dist/mcp/protocol.js +149 -0
  86. package/dist/mcp/toolAdapter.d.ts +44 -0
  87. package/dist/mcp/toolAdapter.js +94 -0
  88. package/dist/mcp/transport.d.ts +109 -0
  89. package/dist/mcp/transport.js +383 -0
  90. package/dist/memory/embedders/hash.d.ts +12 -0
  91. package/dist/memory/embedders/hash.js +31 -0
  92. package/dist/memory/embedders/http.d.ts +25 -0
  93. package/dist/memory/embedders/http.js +48 -0
  94. package/dist/memory/memory.d.ts +19 -2
  95. package/dist/memory/memory.js +75 -11
  96. package/dist/memory/semantic.d.ts +17 -0
  97. package/dist/memory/semantic.js +29 -0
  98. package/dist/orchestration/budget.d.ts +30 -0
  99. package/dist/orchestration/budget.js +40 -0
  100. package/dist/orchestration/executor.d.ts +64 -1
  101. package/dist/orchestration/executor.js +104 -7
  102. package/dist/orchestration/orchestrator.d.ts +35 -1
  103. package/dist/orchestration/orchestrator.js +106 -8
  104. package/dist/orchestration/plan.d.ts +15 -1
  105. package/dist/orchestration/plan.js +23 -4
  106. package/dist/orchestration/planner.d.ts +19 -1
  107. package/dist/orchestration/planner.js +25 -5
  108. package/dist/plugin/ai.d.ts +4 -0
  109. package/dist/plugin/ai.js +9 -0
  110. package/dist/runtime/config.js +50 -6
  111. package/dist/runtime/intent/aiClassifier.d.ts +19 -0
  112. package/dist/runtime/intent/aiClassifier.js +74 -0
  113. package/dist/runtime/models/modelProfile.d.ts +61 -0
  114. package/dist/runtime/models/modelProfile.js +139 -0
  115. package/dist/runtime/planning/deriveCapabilities.d.ts +95 -0
  116. package/dist/runtime/planning/deriveCapabilities.js +146 -0
  117. package/dist/runtime/policy.d.ts +10 -0
  118. package/dist/runtime/policy.js +9 -2
  119. package/dist/runtime/runtime.d.ts +233 -0
  120. package/dist/runtime/runtime.js +1042 -60
  121. package/dist/runtime/types.d.ts +88 -2
  122. package/dist/security/redact.js +22 -10
  123. package/dist/skills/manifest.d.ts +3 -0
  124. package/dist/skills/manifest.js +24 -0
  125. package/dist/skills/registry.d.ts +16 -1
  126. package/dist/skills/registry.js +21 -1
  127. package/dist/skills/skill.d.ts +6 -1
  128. package/dist/store/area.d.ts +15 -1
  129. package/dist/store/area.js +19 -8
  130. package/dist/store/crypto.d.ts +21 -0
  131. package/dist/store/crypto.js +49 -0
  132. package/dist/store/paths.d.ts +5 -1
  133. package/dist/store/paths.js +6 -0
  134. package/dist/store/store.d.ts +15 -3
  135. package/dist/store/store.js +28 -7
  136. package/dist/telemetry/sinks/otlp.d.ts +31 -0
  137. package/dist/telemetry/sinks/otlp.js +76 -0
  138. package/dist/tools/builtins/filesystem.js +1 -0
  139. package/dist/tools/builtins/git.js +1 -0
  140. package/dist/tools/builtins/shell.js +1 -0
  141. package/dist/tools/permissions.d.ts +28 -0
  142. package/dist/tools/permissions.js +72 -0
  143. package/dist/tools/registry.d.ts +18 -2
  144. package/dist/tools/registry.js +22 -2
  145. package/dist/tools/tool.d.ts +4 -0
  146. package/dist/types.d.ts +5 -1
  147. package/dist/util/flatten.d.ts +11 -0
  148. package/dist/util/flatten.js +18 -0
  149. package/dist/util/hash.d.ts +19 -0
  150. package/dist/util/hash.js +39 -0
  151. package/dist/util/semaphore.d.ts +19 -0
  152. package/dist/util/semaphore.js +60 -0
  153. package/package.json +24 -9
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Persisted agent tasks (Phase 3.5) — the schema, and the ONE way anything reads them back.
3
+ *
4
+ * An `Execution` is a JSON file in the user's home. Invariant 21 says persisted agent state is
5
+ * schema-validated on read, and the reason is concrete: a record's `innerPlan` is EXECUTED on resume and
6
+ * its hashes decide whether completed work may be reused, so a malformed record is not a display problem
7
+ * — it is an execution problem. Validation is fail-closed per record: anything that does not parse is
8
+ * dropped whole and counted, never coerced or half-read.
9
+ *
10
+ * READING IS NOT WRITING. `parseAgentTasks` returns a filtered VIEW and never touches the stored record,
11
+ * so merely listing or acquiring an execution cannot destroy tasks it could not parse. The dropped count
12
+ * travels with the view so a silently shrinking list is visible instead of inferred.
13
+ */
14
+ import { z } from 'zod';
15
+ /** Structural only. A persisted inner plan still faces `validatePlan` against the envelope before it runs. */
16
+ const planStep = z
17
+ .object({
18
+ id: z.string().min(1),
19
+ description: z.string(),
20
+ skill: z.string().optional(),
21
+ tool: z.string().optional(),
22
+ agent: z.string().optional(),
23
+ motivatedBy: z.array(z.string()).optional(),
24
+ input: z.unknown().optional(),
25
+ dependsOn: z.array(z.string()).optional(),
26
+ status: z.enum(['pending', 'running', 'succeeded', 'failed', 'skipped']),
27
+ })
28
+ .strict();
29
+ const executionPlan = z.object({ id: z.string(), goal: z.string(), version: z.number(), steps: z.array(planStep), reason: z.string().optional() }).strict();
30
+ const observation = z
31
+ .object({
32
+ stepId: z.string(),
33
+ skill: z.string().optional(),
34
+ tool: z.string().optional(),
35
+ agent: z.string().optional(),
36
+ agentTaskId: z.string().optional(),
37
+ ok: z.boolean(),
38
+ output: z.string().optional(),
39
+ error: z.string().optional(),
40
+ code: z.string().optional(),
41
+ data: z.unknown().optional(),
42
+ artifacts: z.array(z.unknown()).optional(),
43
+ callsUsed: z.number().optional(),
44
+ })
45
+ .passthrough();
46
+ const evidence = z.object({ kind: z.string(), ref: z.string().optional(), detail: z.string().optional(), stepId: z.string().optional() }).passthrough();
47
+ const finding = z
48
+ .object({
49
+ id: z.string().min(1),
50
+ agentId: z.string(),
51
+ agentTaskId: z.string(),
52
+ type: z.string(),
53
+ subject: z.string().optional(),
54
+ claim: z.string(),
55
+ verdict: z.string().optional(),
56
+ executionCoverage: z.number().min(0).max(1),
57
+ confidence: z.number().min(0).max(1),
58
+ evidence: z.array(evidence),
59
+ artifacts: z.array(z.unknown()),
60
+ sourceSteps: z.array(z.string()),
61
+ provenance: z.object({ executionId: z.string().optional(), planVersion: z.number() }).passthrough(),
62
+ status: z.enum(['active', 'superseded', 'contradicted']),
63
+ supersededBy: z.string().optional(),
64
+ createdAt: z.number(),
65
+ })
66
+ .passthrough();
67
+ const checkpoint = z
68
+ .object({
69
+ at: z.number(),
70
+ planVersion: z.number(),
71
+ gitHead: z.string().optional(),
72
+ fileHashes: z.record(z.string(), z.string()),
73
+ configHash: z.string().optional(),
74
+ skillVersions: z.record(z.string(), z.string()),
75
+ completedSteps: z.array(z.string()),
76
+ mcpTools: z.record(z.string(), z.string()).optional(),
77
+ })
78
+ .passthrough();
79
+ /** A counter read back into arithmetic: a whole number, never negative, never absurd. */
80
+ const count = z.number().int().min(0).max(1_000_000);
81
+ /** Bounds on the per-record collections, so one task cannot make an execution file unreadable. */
82
+ const FINDINGS_MAX = 200;
83
+ const DIAGNOSTICS_MAX = 200;
84
+ const AGENT_STATES = ['created', 'queued', 'running', 'completed', 'failed', 'cancelled', 'waiting_for_input', 'waiting_for_clarification', 'paused'];
85
+ /**
86
+ * The record schema. `v` is a LITERAL: a record written by a future, wider version is dropped rather
87
+ * than parsed as this shape — reinterpreting an unknown layout is how a resume executes something its
88
+ * author never wrote.
89
+ */
90
+ export const persistedAgentTask = z
91
+ .object({
92
+ v: z.literal(1),
93
+ agentTaskId: z.string().min(1),
94
+ agentId: z.string().min(1),
95
+ stepId: z.string().min(1),
96
+ state: z.enum(AGENT_STATES),
97
+ createdAt: z.number(),
98
+ updatedAt: z.number(),
99
+ startedAt: z.number().optional(),
100
+ endedAt: z.number().optional(),
101
+ interruption: z.object({ kind: z.enum(['crash', 'pause', 'parent-cancel']), at: z.number(), detail: z.string().optional() }).strict().optional(),
102
+ provenance: z.object({ executionId: z.string().optional(), planVersion: z.number() }).strict(),
103
+ agentDefHash: z.string().min(1),
104
+ envelopeHash: z.string().min(1),
105
+ stepInputHash: z.string().min(1),
106
+ attempt: z.number().int().min(1),
107
+ innerPlanVersion: z.number().optional(),
108
+ innerPlan: executionPlan.optional(),
109
+ innerCompletedSteps: z.array(z.string()),
110
+ innerObservations: z.array(observation),
111
+ innerObservationsOmitted: z.number().int().min(0),
112
+ innerCheckpoint: checkpoint.optional(),
113
+ pendingInner: z.object({ kind: z.literal('clarification'), question: z.string(), at: z.number() }).strict().optional(),
114
+ // Bounded and non-negative. These are read back into budget arithmetic and into rendered status
115
+ // lines, so a tampered or corrupted file must not be able to inject a negative or absurd count.
116
+ innerSteps: z.object({ total: count, succeeded: count }).strict(),
117
+ callsReserved: count,
118
+ callsUsed: count,
119
+ callsRefunded: count,
120
+ toolCallsUsed: count,
121
+ findings: z.array(finding).max(FINDINGS_MAX),
122
+ diagnostics: z.array(z.unknown()).max(DIAGNOSTICS_MAX),
123
+ failure: z.object({ code: z.string(), message: z.string() }).passthrough().optional(),
124
+ })
125
+ .strict();
126
+ /**
127
+ * Validate the agent tasks on an execution. Pure: it reads, it never writes. Callers that then persist
128
+ * the execution are choosing to drop the unparseable records — reading alone never does.
129
+ */
130
+ export function parseAgentTasks(exec) {
131
+ const raw = exec.agentTasks;
132
+ if (!Array.isArray(raw))
133
+ return { tasks: [], dropped: 0 };
134
+ const tasks = [];
135
+ let dropped = 0;
136
+ for (const entry of raw) {
137
+ const parsed = persistedAgentTask.safeParse(entry);
138
+ if (!parsed.success) {
139
+ dropped += 1;
140
+ continue;
141
+ }
142
+ tasks.push(parsed.data);
143
+ }
144
+ return { tasks, dropped };
145
+ }
146
+ /** One task by id, validated — the lookup every resume path uses. */
147
+ export function findAgentTask(exec, agentTaskId) {
148
+ return parseAgentTasks(exec).tasks.find((t) => t.agentTaskId === agentTaskId);
149
+ }
@@ -16,6 +16,10 @@ export interface CaptureInput {
16
16
  skills: Skill[];
17
17
  completedSteps: string[];
18
18
  clock?: Clock;
19
+ /** Phase 3.5: plan-referenced MCP tool id → a hash of its live declaration. An MCP server is remote
20
+ * and mutable: it can change a tool's schema, or drop it, while a plan sits paused. That is drift in
21
+ * exactly the same sense as an edited file, and it was invisible before. */
22
+ mcpTools?: Record<string, string>;
19
23
  }
20
24
  export declare function captureCheckpoint(input: CaptureInput): Checkpoint;
21
25
  export interface Reconciliation {
@@ -23,4 +27,4 @@ export interface Reconciliation {
23
27
  reasons: string[];
24
28
  }
25
29
  /** Recompute the fingerprint and compare to a checkpoint. Any difference is drift. */
26
- export declare function reconcile(checkpoint: Checkpoint, root: string, skills: Skill[]): Reconciliation;
30
+ export declare function reconcile(checkpoint: Checkpoint, root: string, skills: Skill[], mcpTools?: Record<string, string>): Reconciliation;
@@ -85,10 +85,11 @@ export function captureCheckpoint(input) {
85
85
  ...(cfg !== undefined ? { configHash: cfg } : {}),
86
86
  skillVersions,
87
87
  completedSteps: [...input.completedSteps],
88
+ ...(input.mcpTools && Object.keys(input.mcpTools).length ? { mcpTools: input.mcpTools } : {}),
88
89
  };
89
90
  }
90
91
  /** Recompute the fingerprint and compare to a checkpoint. Any difference is drift. */
91
- export function reconcile(checkpoint, root, skills) {
92
+ export function reconcile(checkpoint, root, skills, mcpTools) {
92
93
  const reasons = [];
93
94
  // Compare directly so an ADDITION (absent at capture, present now — e.g. git initialized, config
94
95
  // added) also counts as drift, not just a change to something already captured.
@@ -110,5 +111,16 @@ export function reconcile(checkpoint, root, skills) {
110
111
  else if (cur !== ver)
111
112
  reasons.push(`skill ${id} version changed (${ver} → ${cur})`);
112
113
  }
114
+ // Phase 3.5: an absent `mcpTools` means the checkpoint predates this dimension — "nothing to
115
+ // compare", never "everything changed". A tool that vanished is drift: the plan references it.
116
+ if (checkpoint.mcpTools) {
117
+ for (const [id, hash] of Object.entries(checkpoint.mcpTools)) {
118
+ const cur = mcpTools?.[id];
119
+ if (cur === undefined)
120
+ reasons.push(`MCP tool ${id} is no longer available`);
121
+ else if (cur !== hash)
122
+ reasons.push(`MCP tool ${id} changed shape`);
123
+ }
124
+ }
113
125
  return { drifted: reasons.length > 0, reasons };
114
126
  }
@@ -7,6 +7,7 @@
7
7
  import type { ExecutionPlan } from '../orchestration/plan.js';
8
8
  import type { StepObservation } from '../orchestration/executor.js';
9
9
  import type { ArtifactRef } from '../runtime/types.js';
10
+ import type { AgentTaskRecord } from '../agents/task.js';
10
11
  export type ExecutionStatus = 'planned' | 'running' | 'paused' | 'waiting_for_input' | 'waiting_for_clarification' | 'completed' | 'failed' | 'cancelled';
11
12
  /** A time-boxed claim on an execution by one process. */
12
13
  export interface Lease {
@@ -23,12 +24,31 @@ export interface Checkpoint {
23
24
  configHash?: string;
24
25
  skillVersions: Record<string, string>;
25
26
  completedSteps: string[];
27
+ /** Phase 3.5: plan-referenced MCP tool id → a hash of its declaration at plan time. A server that
28
+ * changed a tool's shape under a paused plan is drift, exactly like an edited file. Absent on every
29
+ * pre-3.5 checkpoint, which reconcile must read as "nothing to compare", never as "everything changed". */
30
+ mcpTools?: Record<string, string>;
26
31
  }
27
- /** What an execution is waiting for (approval or clarification), persisted so a restart can resume it. */
32
+ /** A plan's estimated vs available model-call budget (Phase 22), carried on a budget-paused execution. */
33
+ export interface BudgetInfo {
34
+ /** Estimated model calls the plan needs (its skill steps). */
35
+ estCalls: number;
36
+ /** The call budget in force (`policy.maxCalls`). */
37
+ maxCalls: number;
38
+ /** Steps already completed (0 on a notify-before-start pause). */
39
+ completedSteps: number;
40
+ totalSteps: number;
41
+ }
42
+ /** What an execution is waiting for (approval / clarification / budget), persisted so a restart resumes it. */
28
43
  export interface PendingInput {
29
- kind: 'approval' | 'clarification';
44
+ kind: 'approval' | 'clarification' | 'budget';
30
45
  question?: string;
31
46
  action?: string;
47
+ budget?: BudgetInfo;
48
+ /** Phase 3.5: set when the wait belongs to an AGENT's inner run. The answer is then routed into that
49
+ * task's inner resume — appending it to the OUTER goal would replan and destroy every sibling's
50
+ * progress. Absent ⇒ the wait is the outer run's, which is the pre-3.5 behaviour unchanged. */
51
+ agentTaskId?: string;
32
52
  }
33
53
  export interface Execution {
34
54
  id: string;
@@ -46,6 +66,20 @@ export interface Execution {
46
66
  createdAt: number;
47
67
  updatedAt: number;
48
68
  lease?: Lease;
69
+ /** Phase 3.5: budgeted step-calls charged across every run AND resume of this execution — the unit
70
+ * `foldCalls` counts, NOT provider requests (planning calls are deliberately outside the pool).
71
+ *
72
+ * ACCOUNTING, NOT A CAP. It is deliberately not subtracted from `maxCalls` on resume: `AI_MAX_CALLS`
73
+ * bounds a RUN, and "raise it and resume" is how a partially-budgeted plan makes progress (Phase 22).
74
+ * Treating it as a cumulative task ceiling would make `AI_MAX_CALLS=1` un-resumable — the first run
75
+ * spends the whole allowance and every resume gets a budget of zero. See DECISIONS D132.
76
+ * Optional because records written before 3.5 do not have it: always read `?? 0`. */
77
+ callsUsed?: number;
78
+ /** Phase 3.5: the agent tasks this execution owns, in creation order. Schema-validated on read. */
79
+ agentTasks?: AgentTaskRecord[];
80
+ /** Phase 3.5, diagnostic: how many persisted agent tasks failed validation on the last read. Counted
81
+ * so a silently shrinking list is visible; the dropped records are never rendered as content. */
82
+ agentTasksDropped?: number;
49
83
  }
50
84
  export declare const RESUMABLE: ReadonlySet<ExecutionStatus>;
51
85
  export declare const TERMINAL: ReadonlySet<ExecutionStatus>;
@@ -21,6 +21,26 @@ export interface AcquireResult {
21
21
  execution?: Execution;
22
22
  reason?: string;
23
23
  }
24
+ /**
25
+ * Why a mid-run progress commit was accepted or refused (Phase 3.5).
26
+ *
27
+ * Every refusal means the same thing to the caller: SOMEONE ELSE now owns this execution's fate, and
28
+ * this process must stop — not retry, not carry on discarding writes. A run that keeps executing after a
29
+ * refused commit doubles every tool side effect and every model call while another owner re-runs the
30
+ * same steps, so the caller is required to abort. That is why this is an enum and not a boolean: a
31
+ * boolean invites an ignored return value at the call site, which is exactly the bug.
32
+ */
33
+ export type CommitOutcome = 'ok'
34
+ /** The record is gone from disk (deleted under us). */
35
+ | 'missing'
36
+ /** Another owner holds the lease, or ours is gone — writing would clobber a live run. */
37
+ | 'foreign-lease'
38
+ /** The lease was released out from under us. Never write leaseless: an unleased record is claimable. */
39
+ | 'released'
40
+ /** Disk reached a terminal status. TERMINAL IS STICKY — a snapshot from before it may not undo it. */
41
+ | 'terminal'
42
+ /** Someone paused us. `paused` is not terminal, but this owner cannot produce it, so it is not ours to overwrite. */
43
+ | 'paused';
24
44
  export declare class ExecutionStore {
25
45
  private readonly area;
26
46
  readonly owner: string;
@@ -42,6 +62,23 @@ export declare class ExecutionStore {
42
62
  /** Claim an execution. Fails if a DIFFERENT owner holds a live lease; steals an expired one. Re-reads
43
63
  * after writing to detect losing a concurrent steal (best-effort — no OS lock). */
44
64
  acquire(id: string): AcquireResult;
65
+ /**
66
+ * THE mid-run write (Phase 3.5). Every incremental commit point goes through here and nothing else
67
+ * writes an execution while a run is in flight, so all four ordering rules live in one place:
68
+ *
69
+ * 1. TERMINAL IS STICKY. A commit carries a snapshot taken before it was built; if the record reached
70
+ * `completed`/`failed`/`cancelled` in the meantime, that verdict stands. Without this, a cancel is
71
+ * undone by the next in-flight wave's commit milliseconds later.
72
+ * 2. `paused` IS NOT OURS TO OVERWRITE. A running owner never writes `paused`, so finding it on disk
73
+ * means someone else did — and the pre-3.5 behaviour (clobber it at end of run) is far worse here,
74
+ * because commits are frequent: the pause would be erased within one batch, before any heartbeat
75
+ * tick could observe it.
76
+ * 3. NEVER WRITE LEASELESS. An unleased record is claimable by any process; writing one back without
77
+ * a lease invites a second owner to acquire and re-run everything this owner is still doing.
78
+ * 4. THE DISK LEASE WINS. Lease bookkeeping belongs to acquire/heartbeat; a progress commit carries a
79
+ * possibly-stale copy and must not push it back.
80
+ */
81
+ commitProgress(exec: Execution): CommitOutcome;
45
82
  /** Write only if we still hold the lease (or it's free/expired) — never clobber a live different owner. */
46
83
  commit(exec: Execution): boolean;
47
84
  /** Renew this owner's lease. Returns false if we no longer hold it. */
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { hostname } from 'node:os';
11
11
  import { systemClock } from '../util/clock.js';
12
+ import { TERMINAL } from './execution.js';
12
13
  export class ExecutionStore {
13
14
  area;
14
15
  owner;
@@ -85,6 +86,38 @@ export class ExecutionStore {
85
86
  return { ok: false, reason: 'lost the acquire race' };
86
87
  return { ok: true, execution: confirmed };
87
88
  }
89
+ /**
90
+ * THE mid-run write (Phase 3.5). Every incremental commit point goes through here and nothing else
91
+ * writes an execution while a run is in flight, so all four ordering rules live in one place:
92
+ *
93
+ * 1. TERMINAL IS STICKY. A commit carries a snapshot taken before it was built; if the record reached
94
+ * `completed`/`failed`/`cancelled` in the meantime, that verdict stands. Without this, a cancel is
95
+ * undone by the next in-flight wave's commit milliseconds later.
96
+ * 2. `paused` IS NOT OURS TO OVERWRITE. A running owner never writes `paused`, so finding it on disk
97
+ * means someone else did — and the pre-3.5 behaviour (clobber it at end of run) is far worse here,
98
+ * because commits are frequent: the pause would be erased within one batch, before any heartbeat
99
+ * tick could observe it.
100
+ * 3. NEVER WRITE LEASELESS. An unleased record is claimable by any process; writing one back without
101
+ * a lease invites a second owner to acquire and re-run everything this owner is still doing.
102
+ * 4. THE DISK LEASE WINS. Lease bookkeeping belongs to acquire/heartbeat; a progress commit carries a
103
+ * possibly-stale copy and must not push it back.
104
+ */
105
+ commitProgress(exec) {
106
+ const disk = this.get(exec.id);
107
+ if (!disk)
108
+ return 'missing';
109
+ if (TERMINAL.has(disk.status))
110
+ return 'terminal';
111
+ if (disk.status === 'paused' && exec.status !== 'paused')
112
+ return 'paused';
113
+ if (!disk.lease)
114
+ return 'released';
115
+ if (disk.lease.owner !== this.owner)
116
+ return 'foreign-lease';
117
+ exec.lease = disk.lease;
118
+ this.save(exec);
119
+ return 'ok';
120
+ }
88
121
  /** Write only if we still hold the lease (or it's free/expired) — never clobber a live different owner. */
89
122
  commit(exec) {
90
123
  const disk = this.get(exec.id);
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { type OpenApiAnalysis } from '../discovery/openapi.js';
8
8
  import type { ProviderConfig } from '../types.js';
9
+ import type { AI } from '../plugin/ai.js';
9
10
  export interface GenerateOptions {
10
11
  id?: string;
11
12
  apiKeyEnv?: string;
@@ -15,3 +16,16 @@ export interface GenerateOptions {
15
16
  /** Turn an OpenAPI analysis into a ProviderConfig. Throws if the spec yielded no usable mapping. */
16
17
  export declare function generateProviderConfig(analysis: OpenApiAnalysis, overrides?: GenerateOptions): ProviderConfig;
17
18
  export declare function generateProviderConfigFromFile(specPath: string, overrides?: GenerateOptions): ProviderConfig;
19
+ export interface SuggestResult {
20
+ ok: boolean;
21
+ config?: ProviderConfig;
22
+ error?: string;
23
+ }
24
+ /**
25
+ * AI-assisted adapter rung (Phase 21a) — an opt-in gap-filler on top of the DETERMINISTIC
26
+ * `generateProviderConfig`. When the spec doesn't enumerate models, a model proposes a likely model-id
27
+ * list; the OUTPUT is STILL produced by `generateProviderConfig` (a validated `ProviderConfig`, never code),
28
+ * so a bad suggestion can only propose model-id strings the operator then confirms — it can never inject an
29
+ * unvalidated config. Any model/parse failure falls back to the deterministic result (or its error).
30
+ */
31
+ export declare function suggestProviderConfig(analysis: OpenApiAnalysis, ai: AI, overrides?: GenerateOptions): Promise<SuggestResult>;
@@ -6,6 +6,8 @@
6
6
  */
7
7
  import { AIError } from '../core/fallback/errors.js';
8
8
  import { analyzeOpenApiFile } from '../discovery/openapi.js';
9
+ import { extractJson } from '../util/extractJson.js';
10
+ import { wrapUntrusted } from '../tools/untrusted.js';
9
11
  /** Turn an OpenAPI analysis into a ProviderConfig. Throws if the spec yielded no usable mapping. */
10
12
  export function generateProviderConfig(analysis, overrides = {}) {
11
13
  const base = analysis.suggestedProviderConfig;
@@ -28,3 +30,39 @@ export function generateProviderConfig(analysis, overrides = {}) {
28
30
  export function generateProviderConfigFromFile(specPath, overrides = {}) {
29
31
  return generateProviderConfig(analyzeOpenApiFile(specPath), overrides);
30
32
  }
33
+ /**
34
+ * AI-assisted adapter rung (Phase 21a) — an opt-in gap-filler on top of the DETERMINISTIC
35
+ * `generateProviderConfig`. When the spec doesn't enumerate models, a model proposes a likely model-id
36
+ * list; the OUTPUT is STILL produced by `generateProviderConfig` (a validated `ProviderConfig`, never code),
37
+ * so a bad suggestion can only propose model-id strings the operator then confirms — it can never inject an
38
+ * unvalidated config. Any model/parse failure falls back to the deterministic result (or its error).
39
+ */
40
+ export async function suggestProviderConfig(analysis, ai, overrides = {}) {
41
+ let modelList = Array.isArray(overrides.models) ? overrides.models : [];
42
+ // Only consult the model when we actually have a gap to fill (no explicit models, none in the spec).
43
+ const specModels = analysis.suggestedProviderConfig?.models;
44
+ const needsModels = modelList.length === 0 && (specModels === undefined || specModels === 'auto');
45
+ if (needsModels) {
46
+ try {
47
+ const prompt = `An OpenAPI spec describes an AI provider. Suggest the model IDs it most likely exposes for chat/completions. ` +
48
+ `Reply ONLY with JSON: {"models": ["<model-id>", ...]}.\n\n` +
49
+ wrapUntrusted('openapi-summary', `title: ${analysis.title ?? '(unknown)'}\nbaseUrl: ${analysis.baseUrl ?? '(unknown)'}\nwireShape: ${analysis.wireShape}\nendpoints: ${analysis.endpoints.map((e) => e.path).slice(0, 20).join(', ')}`);
50
+ const run = await ai.run({ task: 'suggest-provider-config', input: prompt, output: { format: 'json' }, requirements: { intelligence: ['reasoning'], output: ['json'] } });
51
+ if (run.ok && run.response) {
52
+ const parsed = (run.response.json ?? extractJson(run.response.text ?? ''));
53
+ if (Array.isArray(parsed?.models))
54
+ modelList = parsed.models.filter((m) => typeof m === 'string' && m.trim().length > 0);
55
+ }
56
+ }
57
+ catch {
58
+ /* a suggestion failure just falls through to the deterministic path */
59
+ }
60
+ }
61
+ try {
62
+ const config = generateProviderConfig(analysis, { ...overrides, ...(modelList.length && !overrides.models ? { models: modelList } : {}) });
63
+ return { ok: true, config };
64
+ }
65
+ catch (e) {
66
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
67
+ }
68
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Skill scaffolding (Phase 21a) — the generative seam for skills, mirroring `generateAdapter`'s
3
+ * "emit validated CONFIG/manifest, never code" rule. A model drafts a `*.skill.yaml` MANIFEST (pure data,
4
+ * parsed by the existing `parseManifest`); we then validate it three ways before offering it:
5
+ * 1. `parseManifest` (shape),
6
+ * 2. every step names an AVAILABLE tool (a `validatePlan`-style membership check — no invented tools),
7
+ * 3. a dry `manifestToSkill` compile (it must not throw).
8
+ * Nothing is written here — the caller writes it only on explicit confirm into the consented
9
+ * `.ai-runtime/skills/` directory. A drafted manifest that fails any check is refused with a reason.
10
+ */
11
+ import type { AI } from '../plugin/ai.js';
12
+ import { type SkillManifest } from '../skills/manifest.js';
13
+ export interface GenerateSkillInput {
14
+ goal: string;
15
+ /** Tool ids the drafted manifest may use (the host's registered tools). */
16
+ tools: string[];
17
+ ai: AI;
18
+ }
19
+ export interface GenerateSkillResult {
20
+ ok: boolean;
21
+ manifest?: SkillManifest;
22
+ /** The manifest serialized as `*.skill.yaml`, ready to write on confirm. */
23
+ yaml?: string;
24
+ error?: string;
25
+ }
26
+ export declare function generateSkillManifest(input: GenerateSkillInput): Promise<GenerateSkillResult>;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Skill scaffolding (Phase 21a) — the generative seam for skills, mirroring `generateAdapter`'s
3
+ * "emit validated CONFIG/manifest, never code" rule. A model drafts a `*.skill.yaml` MANIFEST (pure data,
4
+ * parsed by the existing `parseManifest`); we then validate it three ways before offering it:
5
+ * 1. `parseManifest` (shape),
6
+ * 2. every step names an AVAILABLE tool (a `validatePlan`-style membership check — no invented tools),
7
+ * 3. a dry `manifestToSkill` compile (it must not throw).
8
+ * Nothing is written here — the caller writes it only on explicit confirm into the consented
9
+ * `.ai-runtime/skills/` directory. A drafted manifest that fails any check is refused with a reason.
10
+ */
11
+ import { stringify as toYaml } from 'yaml';
12
+ import { parseManifest, manifestToSkill } from '../skills/manifest.js';
13
+ import { wrapUntrusted } from '../tools/untrusted.js';
14
+ import { extractJson } from '../util/extractJson.js';
15
+ export async function generateSkillManifest(input) {
16
+ const goal = String(input.goal ?? '').trim();
17
+ if (!goal)
18
+ return { ok: false, error: 'a goal is required' };
19
+ const tools = input.tools ?? [];
20
+ if (tools.length === 0)
21
+ return { ok: false, error: 'no tools are available to compose a skill from' };
22
+ const prompt = `Draft an ai-runtime skill MANIFEST (pure data — NOT code) that achieves the goal below. ` +
23
+ `The manifest runs a sequence of tool steps. Use ONLY these registered tools: ${tools.join(', ')}.\n\n` +
24
+ `Goal:\n${wrapUntrusted('scaffold-goal', goal)}\n\n` +
25
+ `Reply ONLY with JSON of this exact shape: {"id":"<kebab-case-id>","version":"1.0.0","name":"<short name>",` +
26
+ `"description":"<one line>","runtimeCompatibility":">=1.0.0","steps":[{"tool":"<one of the tools>","description":"<what this step does>"}]}.`;
27
+ // The whole model round-trip is wrapped: a bad model reply (e.g. non-JSON, which makes `extractJson`
28
+ // throw) must surface as { ok:false }, never as a thrown error — scaffolding is an assist, not a hard op.
29
+ try {
30
+ const run = await input.ai.run({ task: 'generate-skill', input: prompt, output: { format: 'json' }, requirements: { intelligence: ['reasoning'], output: ['json'] } });
31
+ if (!run.ok || !run.response)
32
+ return { ok: false, error: `generation failed (${run.error?.category ?? 'no response'})` };
33
+ const raw = run.response.json ?? extractJson(run.response.text ?? '');
34
+ const { manifest, error } = parseManifest(raw);
35
+ if (!manifest)
36
+ return { ok: false, error: `drafted manifest is invalid: ${error ?? 'unparseable'}` };
37
+ // Membership: a drafted step may only name a tool the host actually has (no invented capabilities).
38
+ const unknown = [...new Set(manifest.steps.map((s) => s.tool).filter((t) => !tools.includes(t)))];
39
+ if (unknown.length)
40
+ return { ok: false, error: `manifest names unregistered tool(s): ${unknown.join(', ')}` };
41
+ // A safe, filesystem-friendly id (also the filename); reject anything that could escape the skills dir.
42
+ if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(manifest.id))
43
+ return { ok: false, error: `manifest id must be kebab-case ([a-z0-9-]): got ${JSON.stringify(manifest.id)}` };
44
+ // Dry compile — the manifest must produce a runnable Skill without throwing.
45
+ manifestToSkill(manifest, '(scaffold)');
46
+ return { ok: true, manifest, yaml: toYaml(manifest) };
47
+ }
48
+ catch (e) {
49
+ return { ok: false, error: `could not scaffold a skill: ${e instanceof Error ? e.message : String(e)}` };
50
+ }
51
+ }
package/dist/index.d.ts CHANGED
@@ -44,14 +44,20 @@ export { analyzeOpenApi, analyzeOpenApiFile } from './discovery/openapi.js';
44
44
  export type { OpenApiAnalysis } from './discovery/openapi.js';
45
45
  export { benchmark } from './benchmark/benchmark.js';
46
46
  export type { BenchResult, BenchTaskSpec } from './benchmark/benchmark.js';
47
- export { generateProviderConfig, generateProviderConfigFromFile } from './generation/generateAdapter.js';
48
- export type { GenerateOptions } from './generation/generateAdapter.js';
47
+ export { generateProviderConfig, generateProviderConfigFromFile, suggestProviderConfig } from './generation/generateAdapter.js';
48
+ export type { GenerateOptions, SuggestResult } from './generation/generateAdapter.js';
49
+ export { generateSkillManifest } from './generation/generateSkill.js';
50
+ export type { GenerateSkillInput, GenerateSkillResult } from './generation/generateSkill.js';
49
51
  export { McpRegistry, StaticMcpSource } from './mcp/mcp.js';
50
52
  export type { McpToolSource } from './mcp/mcp.js';
51
53
  export { setCredentialResolver, Credential } from './security/credentials.js';
52
54
  export type { CredentialResolver } from './security/credentials.js';
53
55
  export { CallbackSink, MemorySink, MultiSink, NullSink } from './telemetry/telemetry.js';
54
56
  export type { TelemetrySink } from './telemetry/telemetry.js';
57
+ export { FileSink } from './telemetry/sinks/file.js';
58
+ export { OtlpSink } from './telemetry/sinks/otlp.js';
59
+ export type { OtlpSinkOptions } from './telemetry/sinks/otlp.js';
60
+ export { KeyedSemaphore } from './util/semaphore.js';
55
61
  export { MARKETPLACE, listPresets, findPreset, presetToConfig } from './marketplace/presets.js';
56
62
  export type { ProviderPreset } from './marketplace/presets.js';
57
63
  export { loadRemoteConfig, loadConfigAsync } from './config/load.js';
@@ -64,6 +70,7 @@ export { resolvePolicy, defaultAutonomyForMode } from './runtime/policy.js';
64
70
  export type { ExecutionPolicy, PermissionPolicy, Autonomy, ApprovalLevel, PolicyOverrides } from './runtime/policy.js';
65
71
  export { parseRuntimeConfig, loadRuntimeConfig, findRuntimeConfigFile } from './runtime/config.js';
66
72
  export { HeuristicIntentClassifier } from './runtime/intent/classifier.js';
73
+ export { AIIntentClassifier } from './runtime/intent/aiClassifier.js';
67
74
  export type { IntentClassifier, IntentClassification, IntentInput } from './runtime/intent/classifier.js';
68
75
  export { detectWorkspace, summarizeWorkspace } from './runtime/workspace/workspace.js';
69
76
  export type { WorkspaceInfo } from './runtime/workspace/workspace.js';
@@ -77,8 +84,33 @@ export type { AccessState, ProviderView, ModelView, ProviderViewSource } from '.
77
84
  export { RuntimeStore, STORE_VERSION } from './store/store.js';
78
85
  export type { RuntimeStoreOptions, StorePaths } from './store/store.js';
79
86
  export { FileArea, NullArea } from './store/area.js';
80
- export type { Area, IntegrityIssue } from './store/area.js';
81
- export { resolveHome, projectId, repositoryId, findRepoRoot } from './store/paths.js';
87
+ export type { Area, IntegrityIssue, ContentCodec } from './store/area.js';
88
+ export { makeCodec, deriveKey, encryptString, decryptString, ENVELOPE_PREFIX } from './store/crypto.js';
89
+ export { resolveHome, projectId, repositoryId, organizationId, findRepoRoot } from './store/paths.js';
90
+ export { deriveCapabilities, deriveCapabilitiesOffline, candidatesFrom, candidateSlate, DERIVE_MAX_CANDIDATES, DERIVE_MAX_IDS } from './runtime/planning/deriveCapabilities.js';
91
+ export type { CapabilityCandidate, DeriveCapabilitiesInput, DeriveCapabilitiesResult } from './runtime/planning/deriveCapabilities.js';
92
+ export { ActionCapabilityRegistry, capabilityReportFrom } from './capabilities/registry.js';
93
+ export type { ActionCapabilityQuery, CapabilityConfig, RegistryAccessors } from './capabilities/registry.js';
94
+ export { CURATED_VOCABULARY, curated, isCurated } from './capabilities/vocabulary.js';
95
+ export type { CuratedCapability, PermissionHint } from './capabilities/vocabulary.js';
96
+ export type { ActionCapability, ActionCapabilityDecl, ActionCapabilityEffect, ActionCapabilityProvider, ActionCapabilitySource, CapabilityGap, CapabilityGapReason, CapabilityResolution, GapSuggestion, MissingCapabilityReport, ProviderAvailability, } from './capabilities/capability.js';
97
+ export type { AgentDefinition } from './agents/definition.js';
98
+ export type { AgentEnvelope } from './agents/envelope.js';
99
+ export type { AgentTaskRecord, AgentTaskState } from './agents/task.js';
100
+ export type { Finding, FindingEvidence, FindingEvidenceKind, FindingStatus } from './agents/finding.js';
101
+ export type { AdmissionRejection, AdmissionResult } from './agents/admit.js';
102
+ export type { StepObservationCode } from './orchestration/executor.js';
103
+ export { PLAN_STEP_STATUSES } from './orchestration/plan.js';
104
+ export { parseAgentTasks, findAgentTask } from './executions/agentTasks.js';
105
+ export type { AgentTaskView } from './executions/agentTasks.js';
106
+ export { AGENT_TERMINAL, AGENT_RESUMABLE } from './agents/task.js';
107
+ export type { ProgressSnapshot } from './orchestration/executor.js';
108
+ export { mcpToolId } from './mcp/toolAdapter.js';
109
+ export { MCP_PROTOCOL_VERSION } from './mcp/protocol.js';
110
+ export type { McpServerConfig, McpServerState, McpServerStatus, McpTransportKind } from './mcp/manager.js';
111
+ export type { McpServerSettings } from './runtime/types.js';
112
+ export type { McpServerGrant } from './tools/permissions.js';
113
+ export { clampMcpPermissions, clampPermissions } from './tools/permissions.js';
82
114
  export type { StoreScope } from './store/paths.js';
83
115
  export { ConversationStore } from './conversations/conversations.js';
84
116
  export type { ConversationTurn, ConversationMeta, TurnRole } from './conversations/conversations.js';
@@ -87,9 +119,18 @@ export type { MemoryRecord, MemoryScope, MemorySource, RememberInput, SearchOpti
87
119
  export { classifyMemory } from './memory/classifier.js';
88
120
  export type { MemoryCandidate } from './memory/classifier.js';
89
121
  export { bm25Search, tokenize } from './memory/bm25.js';
122
+ export { cosineSimilarity, semanticRank } from './memory/semantic.js';
123
+ export type { VecDoc, RankedHit } from './memory/semantic.js';
124
+ export { HashEmbedder } from './memory/embedders/hash.js';
125
+ export { HttpEmbedder } from './memory/embedders/http.js';
126
+ export type { HttpEmbedderConfig } from './memory/embedders/http.js';
90
127
  export type { MemoryTrace } from './runtime/types.js';
91
128
  export { compileContext } from './context/compiler.js';
92
129
  export type { ContextBlock, CompiledBlock, CompiledContext, CompileMetrics, CompileOptions, ContextValidation, RetentionLevel } from './context/compiler.js';
130
+ export { verifyContextLoss } from './context/lossVerifier.js';
131
+ export type { VerifyLossInput, LossCheck } from './context/lossVerifier.js';
132
+ export { summarizeOverBudget } from './context/summarize.js';
133
+ export type { SummarizeInput } from './context/summarize.js';
93
134
  export { TokenEstimator, estimateTokens } from './context/tokens.js';
94
135
  export { resolveContextBudget, DEFAULT_CONTEXT_TOKENS } from './context/budget.js';
95
136
  export type { ContextBudgetInputs } from './context/budget.js';
@@ -141,6 +182,8 @@ export type { LearningOutcome, LearnedStat, SkillRef } from './learning/learning
141
182
  export { classifyFeedback } from './learning/feedback.js';
142
183
  export type { FeedbackSignal } from './learning/feedback.js';
143
184
  export { resolveRoutingPrefs, withPreferredProviders } from './runtime/routing.js';
185
+ export { parseModelProfile, parseDirective, resolveModelDirective, directiveToOverrides, loadModelProfile } from './runtime/models/modelProfile.js';
186
+ export type { ModelProfile, ModelDirective } from './runtime/models/modelProfile.js';
144
187
  export type { RoutingSources } from './runtime/routing.js';
145
188
  export { isExcluded, isPreferred } from './core/router/routingPrefs.js';
146
189
  export type { RoutingPreferences } from './types.js';