specrails-core 6.4.0 → 6.6.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 (131) hide show
  1. package/dist/agent-runtime/capabilities.d.ts +2 -2
  2. package/dist/agent-runtime/capabilities.js +22 -5
  3. package/dist/agent-runtime/cli-executor.js +8 -3
  4. package/dist/agent-runtime/cli.d.ts +4 -4
  5. package/dist/agent-runtime/cli.js +50 -3
  6. package/dist/agent-runtime/compact/environment.d.ts +50 -4
  7. package/dist/agent-runtime/compact/environment.js +183 -6
  8. package/dist/agent-runtime/compact/step.d.ts +1 -1
  9. package/dist/agent-runtime/efficiency-summary.d.ts +1 -1
  10. package/dist/agent-runtime/efficiency-types.d.ts +1 -1
  11. package/dist/agent-runtime/engine/checkpoint/database.d.ts +15 -15
  12. package/dist/agent-runtime/engine/contracts.d.ts +2 -1
  13. package/dist/agent-runtime/engine/definition-types.d.ts +1 -1
  14. package/dist/agent-runtime/engine/efficiency-summary.d.ts +8 -8
  15. package/dist/agent-runtime/engine/fork.d.ts +1 -1
  16. package/dist/agent-runtime/engine/graph-description.d.ts +16 -16
  17. package/dist/agent-runtime/engine/implementation-compiler.d.ts +68 -68
  18. package/dist/agent-runtime/engine/piece-dependencies.js +4 -4
  19. package/dist/agent-runtime/engine/pieces/implementation-step.d.ts +1 -1
  20. package/dist/agent-runtime/engine/pieces/role-turn.d.ts +5 -0
  21. package/dist/agent-runtime/engine/pieces/role-turn.js +64 -22
  22. package/dist/agent-runtime/engine/pieces/shared.d.ts +2 -2
  23. package/dist/agent-runtime/engine/pieces/verify.js +46 -13
  24. package/dist/agent-runtime/engine/run-status.d.ts +165 -165
  25. package/dist/agent-runtime/engine/runs.d.ts +168 -168
  26. package/dist/agent-runtime/engine/state.d.ts +16 -16
  27. package/dist/agent-runtime/evaluation.d.ts +3 -3
  28. package/dist/agent-runtime/executor-types.d.ts +10 -10
  29. package/dist/agent-runtime/graph/convergence.d.ts +2 -2
  30. package/dist/agent-runtime/graph/convergence.js +11 -0
  31. package/dist/agent-runtime/graph/nodes.js +35 -4
  32. package/dist/agent-runtime/graph/review-policy.d.ts +1 -1
  33. package/dist/agent-runtime/graph/state.d.ts +6 -2
  34. package/dist/agent-runtime/guardrails.d.ts +1 -1
  35. package/dist/agent-runtime/guardrails.js +4 -1
  36. package/dist/agent-runtime/index.d.ts +3 -2
  37. package/dist/agent-runtime/index.js +1 -0
  38. package/dist/agent-runtime/openai-executor.d.ts +9 -9
  39. package/dist/agent-runtime/openspec.d.ts +4 -4
  40. package/dist/agent-runtime/prompts.d.ts +6 -0
  41. package/dist/agent-runtime/prompts.js +32 -8
  42. package/dist/agent-runtime/recovery.d.ts +12 -12
  43. package/dist/agent-runtime/session/application/active-session.d.ts +176 -0
  44. package/dist/agent-runtime/session/application/active-session.js +701 -0
  45. package/dist/agent-runtime/session/application/delegation.d.ts +73 -0
  46. package/dist/agent-runtime/session/application/delegation.js +171 -0
  47. package/dist/agent-runtime/session/application/output-buffer.d.ts +43 -0
  48. package/dist/agent-runtime/session/application/output-buffer.js +65 -0
  49. package/dist/agent-runtime/session/application/session-service.d.ts +121 -0
  50. package/dist/agent-runtime/session/application/session-service.js +256 -0
  51. package/dist/agent-runtime/session/application/timers.d.ts +15 -0
  52. package/dist/agent-runtime/session/application/timers.js +35 -0
  53. package/dist/agent-runtime/session/domain/descriptor.d.ts +6 -0
  54. package/dist/agent-runtime/session/domain/descriptor.js +32 -0
  55. package/dist/agent-runtime/session/domain/errors.d.ts +9 -0
  56. package/dist/agent-runtime/session/domain/errors.js +16 -0
  57. package/dist/agent-runtime/session/domain/events.d.ts +147 -0
  58. package/dist/agent-runtime/session/domain/events.js +24 -0
  59. package/dist/agent-runtime/session/domain/fingerprint.d.ts +2 -0
  60. package/dist/agent-runtime/session/domain/fingerprint.js +10 -0
  61. package/dist/agent-runtime/session/domain/interruption.d.ts +13 -0
  62. package/dist/agent-runtime/session/domain/interruption.js +39 -0
  63. package/dist/agent-runtime/session/domain/policy.d.ts +42 -0
  64. package/dist/agent-runtime/session/domain/policy.js +104 -0
  65. package/dist/agent-runtime/session/domain/snapshot.d.ts +100 -0
  66. package/dist/agent-runtime/session/domain/snapshot.js +234 -0
  67. package/dist/agent-runtime/session/domain/state-machines.d.ts +21 -0
  68. package/dist/agent-runtime/session/domain/state-machines.js +41 -0
  69. package/dist/agent-runtime/session/domain/types.d.ts +130 -0
  70. package/dist/agent-runtime/session/domain/types.js +15 -0
  71. package/dist/agent-runtime/session/domain/usage.d.ts +54 -0
  72. package/dist/agent-runtime/session/domain/usage.js +86 -0
  73. package/dist/agent-runtime/session/drivers/claude/argv.d.ts +19 -0
  74. package/dist/agent-runtime/session/drivers/claude/argv.js +67 -0
  75. package/dist/agent-runtime/session/drivers/claude/driver.d.ts +19 -0
  76. package/dist/agent-runtime/session/drivers/claude/driver.js +134 -0
  77. package/dist/agent-runtime/session/drivers/claude/translator.d.ts +40 -0
  78. package/dist/agent-runtime/session/drivers/claude/translator.js +247 -0
  79. package/dist/agent-runtime/session/drivers/codex/argv.d.ts +12 -0
  80. package/dist/agent-runtime/session/drivers/codex/argv.js +76 -0
  81. package/dist/agent-runtime/session/drivers/codex/driver.d.ts +19 -0
  82. package/dist/agent-runtime/session/drivers/codex/driver.js +193 -0
  83. package/dist/agent-runtime/session/drivers/codex/rpc.d.ts +28 -0
  84. package/dist/agent-runtime/session/drivers/codex/rpc.js +81 -0
  85. package/dist/agent-runtime/session/drivers/codex/translator.d.ts +53 -0
  86. package/dist/agent-runtime/session/drivers/codex/translator.js +267 -0
  87. package/dist/agent-runtime/session/drivers/common/process.d.ts +37 -0
  88. package/dist/agent-runtime/session/drivers/common/process.js +116 -0
  89. package/dist/agent-runtime/session/drivers/executor/driver.d.ts +22 -0
  90. package/dist/agent-runtime/session/drivers/executor/driver.js +163 -0
  91. package/dist/agent-runtime/session/drivers/registry.d.ts +32 -0
  92. package/dist/agent-runtime/session/drivers/registry.js +38 -0
  93. package/dist/agent-runtime/session/host/protocol.d.ts +18 -0
  94. package/dist/agent-runtime/session/host/protocol.js +88 -0
  95. package/dist/agent-runtime/session/host/server.d.ts +57 -0
  96. package/dist/agent-runtime/session/host/server.js +229 -0
  97. package/dist/agent-runtime/session/index.d.ts +45 -0
  98. package/dist/agent-runtime/session/index.js +74 -0
  99. package/dist/agent-runtime/session/journal/schema.d.ts +7 -0
  100. package/dist/agent-runtime/session/journal/schema.js +44 -0
  101. package/dist/agent-runtime/session/journal/sqlite-journal.d.ts +70 -0
  102. package/dist/agent-runtime/session/journal/sqlite-journal.js +279 -0
  103. package/dist/agent-runtime/session/ports.d.ts +192 -0
  104. package/dist/agent-runtime/session/ports.js +1 -0
  105. package/dist/agent-runtime/session/testing/driver-conformance.d.ts +21 -0
  106. package/dist/agent-runtime/session/testing/driver-conformance.js +97 -0
  107. package/dist/agent-runtime/session/testing/fake-clock.d.ts +20 -0
  108. package/dist/agent-runtime/session/testing/fake-clock.js +34 -0
  109. package/dist/agent-runtime/session/testing/fixture-replayer.d.ts +41 -0
  110. package/dist/agent-runtime/session/testing/fixture-replayer.js +172 -0
  111. package/dist/agent-runtime/session/testing/index.d.ts +9 -0
  112. package/dist/agent-runtime/session/testing/index.js +9 -0
  113. package/dist/agent-runtime/session/testing/memory-journal.d.ts +27 -0
  114. package/dist/agent-runtime/session/testing/memory-journal.js +70 -0
  115. package/dist/agent-runtime/session/testing/scripted-driver.d.ts +30 -0
  116. package/dist/agent-runtime/session/testing/scripted-driver.js +77 -0
  117. package/dist/agent-runtime/verification-proposals.d.ts +9 -0
  118. package/dist/agent-runtime/verification-proposals.js +69 -0
  119. package/dist/agent-runtime/verification-repair.d.ts +31 -2
  120. package/dist/agent-runtime/verification-repair.js +58 -3
  121. package/dist/installer/commands/init.d.ts +1 -1
  122. package/dist/installer/phases/install-config.js +10 -3
  123. package/dist/installer/util/paths.d.ts +25 -1
  124. package/dist/installer/util/registry.js +3 -3
  125. package/dist/pipeline/pipeline-state.d.ts +42 -11
  126. package/dist/pipeline/pipeline-state.js +47 -8
  127. package/dist/shared/specrails-home.d.ts +21 -0
  128. package/dist/shared/specrails-home.js +36 -0
  129. package/integration-contract.json +42 -1
  130. package/package.json +23 -11
  131. package/schemas/agent-runtime.schema.json +1 -1
@@ -1,3 +1,10 @@
1
+ // Correction rounds must converge. A fixer round that leaves the change as it
2
+ // was, the same failure surviving round after round, or a review correction
3
+ // that brings back a failure an earlier round had fixed, are loops: repeating
4
+ // the checks cannot change their outcome. The host stops and says why instead
5
+ // of spending another round (observed: verify ↔ fixer ↔ review cycling on a
6
+ // credential problem until the requester cancelled the run).
7
+ import { describeModifiedFiles } from '../../pipeline/pipeline-state.js';
1
8
  import { fingerprint } from '../durable-store.js';
2
9
  /** Verify visits since the latest explicit resume: a human continuing a blocked run grants a fresh budget, as for attempts. */
3
10
  export function outcomesSinceResume(history, checkpoint) {
@@ -23,6 +30,10 @@ export function describeFailure(receipt, roots, finding) {
23
30
  if (finding)
24
31
  return { signature: fingerprint({ finding: normalized(finding, roots) }), summary: finding.slice(0, 300) };
25
32
  const failed = receipt.commands.find(command => command.exitCode !== 0);
33
+ // A green run that rewrote its own candidate: the same files again is the same failure, whatever their content.
34
+ if (!failed && receipt.selfMutation?.files.length)
35
+ return { signature: fingerprint({ selfMutation: receipt.selfMutation.files.map(file => file.repositoryId + ':' + file.path).sort() }),
36
+ summary: `verification commands modified ${describeModifiedFiles(receipt.selfMutation)}`.slice(0, 400) };
26
37
  if (!failed)
27
38
  return { signature: fingerprint({ reason: receipt.reason ?? 'verification failed' }), summary: receipt.reason ?? 'verification failed' };
28
39
  const name = failed.label ?? [failed.command, ...failed.args].join(' ');
@@ -3,12 +3,12 @@ import { verificationFailureSummary } from '../verification-diagnostics.js';
3
3
  import { addDeveloperChecks, bindPlan, expandedPlanCommands, initializeVerificationPlan, readVerificationPlan, validateProposedChecks } from '../verification-plan.js';
4
4
  import path from 'node:path';
5
5
  import { OpenSpecTools } from '../openspec.js';
6
- import { candidateManifest, fingerprintCandidate, frozenAcceptanceCriteria, inspectPipeline, recordAcceptance, transitionPipeline, validateAcceptanceReport, verifyPipeline, } from '../../pipeline/pipeline-state.js';
6
+ import { candidateManifest, describeModifiedFiles, fingerprintCandidate, frozenAcceptanceCriteria, inspectPipeline, recordAcceptance, transitionPipeline, validateAcceptanceReport, verifyPipeline, } from '../../pipeline/pipeline-state.js';
7
7
  import { ARCHITECT_OUTPUT_SCHEMA, DEVELOPER_OUTPUT_SCHEMA, REVIEW_OUTPUT_SCHEMA, correctionInstructions, deepenInstructions, roleInstructions } from '../prompts.js';
8
8
  import { archive, child, journal, object, parseArchitecture, proposedVerification, write, writeDesignConfidence } from './artifacts.js';
9
9
  import { evaluateReview } from './review-policy.js';
10
- import { isEnvironmentFailure } from '../compact/environment.js';
11
- import { hostPreconditionMessage, preconditionBlock, repairEnvironment } from '../verification-repair.js';
10
+ import { describeDrift } from '../compact/environment.js';
11
+ import { adoptedOutputNote, environmentFailure, hostPreconditionMessage, nondeterministicOutputBlocker, preconditionBlock, repairEnvironment, selfMutationOnly } from '../verification-repair.js';
12
12
  import { unreachedTestFiles, unreachedTestsReason } from '../compact/test-reachability.js';
13
13
  import { exitCodeContradiction, exitHonestyReason } from '../compact/exit-code-honesty.js';
14
14
  import { boundedReviewManifest, reviewChanges } from '../review-context.js';
@@ -351,7 +351,11 @@ function verifyNode(deps) {
351
351
  // the model's — for every developer: a fresh worktree has no
352
352
  // node_modules whatever wrote the code. Install once where the failing
353
353
  // command runs and re-verify; only a second failure becomes feedback.
354
- if (!receipt.valid && guardrailEnabled(config.guardrails, 'environment-repair') && receipt.commands.some(command => isEnvironmentFailure(command.exitCode, command.output))) {
354
+ const environment = !receipt.valid && guardrailEnabled(config.guardrails, 'environment-repair') ? environmentFailure(context, receipt) : undefined;
355
+ if (environment) {
356
+ const drift = environment.drift.flatMap(item => item.drift);
357
+ if (drift.length)
358
+ note('developer', `Installed dependencies do not satisfy their declared ranges (${describeDrift(drift)}); the host reinstalls them before judging the failure.`);
355
359
  const repair = repairEnvironment(context, receipt, config.guardrails, text => note('developer', text));
356
360
  if (repair.refused)
357
361
  return hostBlocked(blockerMessage(repair.refused));
@@ -363,9 +367,36 @@ function verifyNode(deps) {
363
367
  return hostBlocked(blockerMessage(precondition));
364
368
  }
365
369
  }
370
+ // Verification that rewrote candidate files itself is not a code failure
371
+ // for the fixer: reverting a regenerated file only makes the next run
372
+ // regenerate it. Same rule as the engine `verify` piece: adopt and verify
373
+ // once more, or stop with the typed blocker when the output keeps moving.
374
+ let adopted = [];
375
+ if (selfMutationOnly(receipt) && !step.signal?.aborted) {
376
+ const nondeterministic = (adoption) => {
377
+ const blocker = nondeterministicOutputBlocker(context, receipt, adoption);
378
+ note('fixer', 'Verification keeps modifying candidate files itself; stopping for the host instead of starting a correction round.');
379
+ return { status: 'blocked', error: `Verification output is not deterministic: ${blocker.reason}. ${blocker.requiredAction} (verification_nondeterministic_output)`, usage: NO_SPEND };
380
+ };
381
+ if (!guardrailEnabled(config.guardrails, 'verification-output-adoption'))
382
+ return nondeterministic(false);
383
+ const first = receipt.selfMutation;
384
+ note('developer', `Verification commands modified ${describeModifiedFiles(first)}; keeping them in the candidate and verifying once more.`);
385
+ receipt = await run();
386
+ if (receipt.valid) {
387
+ adopted = first.files;
388
+ note('developer', adoptedOutputNote(first));
389
+ }
390
+ else if (selfMutationOnly(receipt))
391
+ return nondeterministic(true);
392
+ else if ((precondition = preconditionBlock(context, receipt)))
393
+ return hostBlocked(blockerMessage(precondition));
394
+ }
395
+ const adoptedOutputs = [...new Map([...(state.verifyResult?.adoptedOutputs ?? []), ...adopted].map(file => [file.repositoryId + '\0' + file.path, file])).values()].slice(0, 200);
366
396
  const evidence = {
367
397
  valid: receipt.valid, ...(receipt.reason ? { reason: receipt.reason } : {}), receiptId: receipt.id, unverifiedRepositories: uncovered,
368
398
  commands: receipt.commands.map(command => ({ ...(command.evidenceId ? { evidenceId: command.evidenceId } : {}), repositoryId: command.repositoryId, command: command.command, args: command.args, cwd: command.cwd, exitCode: command.exitCode, failureSummary: verificationFailureSummary(command), output: command.output.slice(-2000) })),
399
+ ...(receipt.selfMutation ? { selfMutation: receipt.selfMutation } : {}), ...(adoptedOutputs.length ? { adoptedOutputs } : {}),
369
400
  };
370
401
  const outcome = (passed, finding) => ({ at: new Date().toISOString(), candidateHash: receipt.candidateHash, planHash: effective.planHash, passed, ...(passed ? {} : describeFailure(receipt, roots, finding)) });
371
402
  /** Hands a failure to the fixer unless the correction loop has stopped converging. */
@@ -1,6 +1,6 @@
1
1
  import type { RuntimeConfig } from '../executor-types.js';
2
2
  import type { ReviewRecord } from './state.js';
3
- export declare const REVIEW_ASPECTS: readonly ["type_correctness", "pattern_adherence", "test_coverage", "security", "architectural_alignment"];
3
+ export declare const REVIEW_ASPECTS: readonly ['type_correctness', 'pattern_adherence', 'test_coverage', 'security', 'architectural_alignment'];
4
4
  export type ReviewAspect = typeof REVIEW_ASPECTS[number];
5
5
  export interface ReviewPolicy {
6
6
  /** Minimum overall score for approval, 0–100. */
@@ -48,6 +48,10 @@ export interface VerificationRecord {
48
48
  failureSummary?: string[];
49
49
  output: string;
50
50
  }>;
51
+ /** Candidate files the verification commands modified in this run (receipt `selfMutation`). */
52
+ selfMutation?: import('../../pipeline/pipeline-state.js').VerificationSelfMutation;
53
+ /** Files verification regenerated that the host kept in the candidate; roles must not revert them. Sticky for the run. */
54
+ adoptedOutputs?: import('../../pipeline/pipeline-state.js').CandidateFileChange[];
51
55
  }
52
56
  export interface ReviewRecord {
53
57
  sessionId?: string;
@@ -74,8 +78,8 @@ export declare const CoreState: import("@langchain/langgraph").AnnotationRoot<{
74
78
  unverifiedRepositories: import("@langchain/langgraph").BaseChannel<string[], string[] | import("@langchain/langgraph").OverwriteValue<string[]>, unknown>;
75
79
  architecture: import("@langchain/langgraph").BaseChannel<ArchitectureRecord | null, ArchitectureRecord | import("@langchain/langgraph").OverwriteValue<ArchitectureRecord | null> | null, unknown>;
76
80
  development: import("@langchain/langgraph").BaseChannel<DeveloperRecord | null, DeveloperRecord | import("@langchain/langgraph").OverwriteValue<DeveloperRecord | null> | null, unknown>;
77
- verifyResult: import("@langchain/langgraph").BaseChannel<VerificationRecord | null, VerificationRecord | import("@langchain/langgraph").OverwriteValue<VerificationRecord | null> | null, unknown>;
78
- review: import("@langchain/langgraph").BaseChannel<ReviewRecord | null, ReviewRecord | import("@langchain/langgraph").OverwriteValue<ReviewRecord | null> | null, unknown>;
81
+ verifyResult: import("@langchain/langgraph").BaseChannel<VerificationRecord | null, import("@langchain/langgraph").OverwriteValue<VerificationRecord | null> | VerificationRecord | null, unknown>;
82
+ review: import("@langchain/langgraph").BaseChannel<ReviewRecord | null, import("@langchain/langgraph").OverwriteValue<ReviewRecord | null> | ReviewRecord | null, unknown>;
79
83
  /** Answers the requester gave to architect questions, oldest first. */
80
84
  answers: import("@langchain/langgraph").BaseChannel<string[], string[] | import("@langchain/langgraph").OverwriteValue<string[]>, unknown>;
81
85
  /** Autonomous investigation passes the architect already spent on a low-confidence design. */
@@ -1,4 +1,4 @@
1
- export declare const GUARDRAIL_IDS: readonly ["plan-validation", "genuine-blocking-question", "plan-language-hint", "frozen-plan-writes", "primary-language", "duplicate-sibling", "one-test-per-module", "empty-write", "evidence-gated-ticking", "inventory-retry", "synthetic-corrections", "silent-group-closure", "verify-per-group", "test-reachability", "exit-code-honesty", "environment-repair", "lockfile-repair", "verify-idle-timeout"];
1
+ export declare const GUARDRAIL_IDS: readonly ['plan-validation', 'genuine-blocking-question', 'plan-language-hint', 'frozen-plan-writes', 'primary-language', 'duplicate-sibling', 'one-test-per-module', 'empty-write', 'evidence-gated-ticking', 'inventory-retry', 'synthetic-corrections', 'silent-group-closure', 'verify-per-group', 'test-reachability', 'exit-code-honesty', 'environment-repair', 'lockfile-repair', 'verify-idle-timeout', 'verification-output-adoption'];
2
2
  export type GuardrailId = (typeof GUARDRAIL_IDS)[number];
3
3
  type GuardrailPhase = 'architect' | 'developer' | 'host';
4
4
  interface GuardrailDescriptor {
@@ -5,7 +5,8 @@
5
5
  // artifacts, one test file per module, evidence before a task is ticked, an
6
6
  // environment repaired by the host instead of the model, a hanging test
7
7
  // stopped by silence, a test file the verify command never runs, a runner
8
- // that prints failures but exits 0. All are ON by default; a project may switch one off in
8
+ // that prints failures but exits 0, a file the verify commands regenerate
9
+ // themselves adopted into the candidate instead of reverted. All are ON by default; a project may switch one off in
9
10
  // its runtime configuration (`guardrails: { <id>: false }`) to tune the
10
11
  // process. Pure bug fixes (candidate fingerprint, correction counter, argument
11
12
  // repair) are deliberately NOT here: they are correctness, not tuning.
@@ -30,6 +31,7 @@ export const GUARDRAIL_IDS = [
30
31
  'environment-repair',
31
32
  'lockfile-repair',
32
33
  'verify-idle-timeout',
34
+ 'verification-output-adoption',
33
35
  ];
34
36
  /** Stable catalog (phase order = pipeline order); labels/descriptions live in the host UI so they can be localized. */
35
37
  export const GUARDRAIL_CATALOG = [
@@ -51,6 +53,7 @@ export const GUARDRAIL_CATALOG = [
51
53
  { id: 'environment-repair', phase: 'host' },
52
54
  { id: 'lockfile-repair', phase: 'host' },
53
55
  { id: 'verify-idle-timeout', phase: 'host' },
56
+ { id: 'verification-output-adoption', phase: 'host' },
54
57
  ];
55
58
  function isGuardrailId(value) {
56
59
  return typeof value === 'string' && GUARDRAIL_IDS.includes(value);
@@ -14,8 +14,9 @@ export { DEFAULT_REVIEW_POLICY, REVIEW_ASPECTS, evaluateReview, resolveReviewPol
14
14
  export { createRoleInvoker, type Accept, type InvokeOptions, type InvokeOutcome, type RoleInvoker, type RoleInvokerDeps } from './graph/roles.js';
15
15
  export { parseAgentObject } from './graph/artifacts.js';
16
16
  export { FileCheckpointSaver, type GraphStoreIO, type SerializedGraphStore } from './graph-checkpointer.js';
17
- export { ARCHITECT_OUTPUT_SCHEMA, DEVELOPER_OUTPUT_SCHEMA, REVIEW_OUTPUT_SCHEMA, ROLE_INSTRUCTIONS_VERSION, rolePromptDefaults, correctionInstructions, deepenInstructions, repairInstructions, roleInstructions, type FrozenCriterion, type RoleFeedback, type RoleInstructionOptions, } from './prompts.js';
17
+ export { ARCHITECT_OUTPUT_SCHEMA, DEVELOPER_OUTPUT_SCHEMA, REVIEW_OUTPUT_SCHEMA, ROLE_INSTRUCTIONS_VERSION, rolePromptDefaults, correctionInstructions, deepenInstructions, repairInstructions, roleInstructions, type AdoptedOutput, type FrozenCriterion, type RoleFeedback, type RoleInstructionOptions, } from './prompts.js';
18
18
  export { OPENSPEC_VERSION, ROLE_SKILLS, type OpenSpecRoleContext } from './openspec.js';
19
19
  export * from './efficiency-summary.js';
20
- export { readVerificationEvidence, type VerificationEvidenceQuery } from '../pipeline/pipeline-state.js';
20
+ export { readVerificationEvidence, type CandidateFileChange, type VerificationEvidenceQuery, type VerificationSelfMutation } from '../pipeline/pipeline-state.js';
21
+ export { NONDETERMINISTIC_OUTPUT_CODE } from './verification-repair.js';
21
22
  export { runRecovery, recoveryRequestSchema, type RecoveryRequest } from './recovery.js';
@@ -18,4 +18,5 @@ export { ARCHITECT_OUTPUT_SCHEMA, DEVELOPER_OUTPUT_SCHEMA, REVIEW_OUTPUT_SCHEMA,
18
18
  export { OPENSPEC_VERSION, ROLE_SKILLS } from './openspec.js';
19
19
  export * from './efficiency-summary.js';
20
20
  export { readVerificationEvidence } from '../pipeline/pipeline-state.js';
21
+ export { NONDETERMINISTIC_OUTPUT_CODE } from './verification-repair.js';
21
22
  export { runRecovery, recoveryRequestSchema } from './recovery.js';
@@ -15,25 +15,25 @@ export declare class OpenAICompatibleExecutor implements AgentExecutor {
15
15
  private readonly options;
16
16
  constructor(provider: ApiProvider, options?: OpenAICompatibleOptions);
17
17
  capabilities(): {
18
- agentLoop: "compact" | "free";
19
- contextWindowTokens: number;
20
- effortSupport: "supported";
21
- supportedEfforts: ("high" | "medium" | "low")[];
22
- continuation: "unsupported";
23
18
  transport: string;
24
19
  resumeRequiresFullContext?: boolean;
25
20
  observedModel: boolean;
26
21
  observedEffort: boolean;
27
- } | {
22
+ continuation: 'unsupported';
23
+ effortSupport: 'supported';
24
+ supportedEfforts: ("high" | "low" | "medium")[];
28
25
  agentLoop: "compact" | "free";
29
26
  contextWindowTokens: number;
30
- effortSupport: "unsupported";
31
- supportedEfforts: never[];
32
- continuation: "unsupported";
27
+ } | {
33
28
  transport: string;
34
29
  resumeRequiresFullContext?: boolean;
35
30
  observedModel: boolean;
36
31
  observedEffort: boolean;
32
+ continuation: 'unsupported';
33
+ effortSupport: 'unsupported';
34
+ supportedEfforts: never[];
35
+ agentLoop: "compact" | "free";
36
+ contextWindowTokens: number;
37
37
  };
38
38
  validateLimits(limits: AgentLimits): void;
39
39
  execute(request: AgentRequest): Promise<AgentResult>;
@@ -2,9 +2,9 @@ import { type AgentRole, type CliProvider, type RoleDescriptor, type AgentAccess
2
2
  import { z } from 'zod';
3
3
  export declare const OPENSPEC_VERSION = "1.4.1";
4
4
  export declare const ROLE_SKILLS: {
5
- readonly architect: "openspec-ff-change";
6
- readonly developer: "openspec-apply-change";
7
- readonly reviewer: "openspec-verify-change";
5
+ readonly architect: 'openspec-ff-change';
6
+ readonly developer: 'openspec-apply-change';
7
+ readonly reviewer: 'openspec-verify-change';
8
8
  };
9
9
  export interface OpenSpecRoleContext {
10
10
  evidenceScope?: {
@@ -112,7 +112,7 @@ export declare class OpenSpecTools {
112
112
  assertParticipation(after?: number): void;
113
113
  assertReady(): Promise<OpenSpecApply>;
114
114
  }
115
- export declare const OPENSPEC_TOOL_ACTIONS: readonly ["load_skill", "new", "status", "instructions", "validate", "write_artifact", "read_progress", "write_progress"];
115
+ export declare const OPENSPEC_TOOL_ACTIONS: readonly ['load_skill', 'new', 'status', 'instructions', 'validate', 'write_artifact', 'read_progress', 'write_progress'];
116
116
  export declare const OPENSPEC_TOOL_DEFINITION: {
117
117
  type: string;
118
118
  function: {
@@ -35,6 +35,12 @@ export interface RoleInstructionOptions {
35
35
  planning?: 'full' | 'proportional';
36
36
  /** The change so far, measured by Core from git against the run's base (rendered lines). */
37
37
  changeSet?: string[];
38
+ /** Files the host verification regenerated and kept in the candidate; merged with `feedback.verification.adoptedOutputs`. */
39
+ adoptedOutputs?: AdoptedOutput[];
40
+ }
41
+ export interface AdoptedOutput {
42
+ repositoryId?: string;
43
+ path: string;
38
44
  }
39
45
  export declare const ARCHITECT_OUTPUT_SCHEMA: Record<string, unknown>;
40
46
  export declare const DEVELOPER_OUTPUT_SCHEMA: Record<string, unknown>;
@@ -178,7 +178,7 @@ function architectSection(verification, definition) {
178
178
  ];
179
179
  }
180
180
  /** The FIXER stance: the developer role on a correction round — repair the exact failure, never re-implement. Shares the developer's verification tail and output contract. */
181
- function fixerSection(verification, definition) {
181
+ function fixerSection(verification, definition, adopted = false) {
182
182
  const lines = definition === undefined ? [
183
183
  '## Your task: correction',
184
184
  '',
@@ -187,7 +187,7 @@ function fixerSection(verification, definition) {
187
187
  '',
188
188
  '1. Read the host failure facts first: command, cwd, original exit code, failureSummary, expected assertion and application file/line. If the excerpt is truncated, use read_verification_evidence with the command\'s evidenceId for complete stdout/stderr and source. Read the failing test and the implementation it exercises.',
189
189
  '2. Reproduce the failure with the narrowest test command. Compare the assertion, intended behavior, current implementation and actual diff. An unchanged file or a pre-existing test does not prove the failure is unrelated: changes can break existing tests indirectly. Classify the cause as an implementation defect, a test compatibility assumption, or an external/scope blocker. A claim that the failure existed before this change needs baseline or historical evidence; otherwise state that it is unconfirmed.',
190
- '3. Patch exactly the files and lines the failure names; touch neighbouring code only when the failure cannot be fixed otherwise. Rewrite a file only when a patch cannot express the change. A correction never grows the blast radius: no new files, dependencies, renames, reformatting or clean-ups; after your turn the diff must be the previous change plus the minimal repair.',
190
+ '3. Patch exactly the files and lines the failure names; touch neighbouring code only when the failure cannot be fixed otherwise. Rewrite a file only when a patch cannot express the change. A correction never grows the blast radius: no new files, dependencies, renames, reformatting or clean-ups; after your turn the diff must be the previous change plus the minimal repair.' + (adopted ? ' Never revert the host-adopted verification output listed below.' : ''),
191
191
  '4. Fix the cause, not the symptom: a repair that satisfies the assertion while leaving the defect, or that special-cases the test input, is not a fix. Fix incorrect implementation behavior. A failing mandatory host test inside the admitted repository/workspace may also receive a minimal compatibility repair when you prove that its expected behavior is unchanged. For example, a source assertion broken only by formatting may tolerate whitespace while retaining every required operand and guard. This applies even to a pre-existing test outside the current changed-file list. Explain the original expectation and why the repair still rejects the prohibited behavior. Never weaken an assertion, delete or skip a test, hardcode success, change acceptance criteria, add dependencies or widen the repair beyond the diagnosed failure.',
192
192
  '5. A test file the host reports as never executed must be wired into the repository\'s test command (the test script or runner configuration) and then made to pass.',
193
193
  '6. Confirm the repair with the focused test command and a negative case for any repaired assertion: removing a required safety guard or returning the wrong value must still fail. Preserve the original test exit status; never pipe test execution through grep/head or another command that masks it. Capture stdout/stderr in a temporary log and inspect that log separately. Core runs the complete verification plan after your turn.',
@@ -196,7 +196,7 @@ function fixerSection(verification, definition) {
196
196
  ] : [definition, ''];
197
197
  return [...lines, ...developerTail(verification)];
198
198
  }
199
- function developerSection(verification, corrections, definition) {
199
+ function developerSection(verification, corrections, definition, adopted = false) {
200
200
  const lines = definition === undefined ? [
201
201
  '## Your task: implementation',
202
202
  '',
@@ -209,7 +209,7 @@ function developerSection(verification, corrections, definition) {
209
209
  '2. Work task by task in order. Use test-driven development: write or extend the test first, make it pass with the smallest correct change, then tidy up. Run only focused tests that cover what you touched while iterating. Core owns the complete verification plan and runs it after your turn; do not duplicate that full run. Fix the precise failures Core returns on a correction pass.',
210
210
  '3. Immediately after completing each task, mark it `- [x]` in `tasks.md`; do not postpone all progress updates until the end of the phase. Only mark tasks whose code and tests are complete. Change nothing else in `tasks.md`, and never edit `proposal.md`, `design.md` or the specs: those documents are frozen, and editing them invalidates the run. If a task cannot be completed, leave it `- [ ]` and list it under `incomplete` with the reason.',
211
211
  '4. Keep the implementation consistent with the repository: naming, error handling, import style, formatting and existing utilities. Do not add dependencies unless the design requires them. Change only what the tasks require: the diff should contain the requested change and its tests, nothing else. Never validate with a temporary configuration, alternate runner or local browser the host verification plan does not use, and never delete such a file to hide it: the host runs the plan as-is. When a required tool is missing, install it through the project\'s documented command or report it as a blocker.',
212
- 'Blast radius: the files design.md and tasks.md name are your boundary. Touch a file outside it only when a task cannot be completed otherwise; keep that edit to the strict need and explain it in summary. Inside a file, change only the lines the task needs: no reformatting, import reordering, renames, type widening, comment rewrites or "while I am here" fixes. Leave an unrelated problem alone and mention it in summary. Keep public signatures, exported contracts, schemas and persisted formats as the design states. Never delete or rewrite a test a task does not name, and never regenerate lockfiles, snapshots or generated files unless the task\'s own change requires it.',
212
+ 'Blast radius: the files design.md and tasks.md name are your boundary. Touch a file outside it only when a task cannot be completed otherwise; keep that edit to the strict need and explain it in summary. Inside a file, change only the lines the task needs: no reformatting, import reordering, renames, type widening, comment rewrites or "while I am here" fixes. Leave an unrelated problem alone and mention it in summary. Keep public signatures, exported contracts, schemas and persisted formats as the design states. Never delete or rewrite a test a task does not name, and never regenerate lockfiles, snapshots or generated files unless the task\'s own change requires it' + (adopted ? ' (the host-adopted verification output listed below is exempt: never revert it).' : '.'),
213
213
  'Quality bar: write code a senior maintainer would merge unchanged. Intention-revealing names from the domain vocabulary; small functions that do one thing at one level of abstraction; guard clauses over nested conditionals; no boolean flag parameters, magic values or hidden side effects; immutability by default and explicit types at module boundaries; parse and validate at the boundary, trust typed values inside; every error path handled the way neighbouring code handles it (fail fast at the boundary, never swallow, errors carry context); domain logic out of adapters and dependencies pointing inward as the repository already does; comments explain why, never what; no dead code, commented-out code, debug output, TODO placeholders or speculative options.',
214
214
  'Engineering judgement: understand before you change and never program by coincidence (if you cannot explain why it works, you are not done); never delete or bypass a guard, branch or workaround you cannot explain. Handle what tests rarely reach: empty, huge and malformed inputs, boundary values, time zones and Unicode, partial failures, retries with idempotency, races, cancellation, timeouts and resource cleanup (handles, listeners, subscriptions, temp files). Security hygiene is non-negotiable: parameterized queries, escaped output, no secrets or personal data in code or logs, least privilege, authorization where the repository enforces it. Logs and metrics follow the repository\'s conventions; migrations and persisted-format changes stay additive and backward compatible. Verify every API, signature and option against the source or installed types, never from memory. Do the simplest thing that fully works, then refactor only inside the blast radius. Tests are the specification: one behavior per test (arrange, act, assert) covering inputs, outputs, side effects and errors; no logic in tests; mock only at real boundaries; deterministic; failing without the change; in the repository\'s existing style. A task is done only when its code, tests and every artifact it makes stale (localized strings in every shipped locale, documentation, schemas, configuration examples) are updated and its focused checks pass.',
215
215
  'Investigation budget: consult the local reference patterns in design.md and equivalent application tests before framework internals or node_modules. After three unsuccessful experiments on the same failure, stop repeating commands: state the hypothesis, evidence and next discriminating experiment, then change approach. If three further experiments add no evidence, report the specific blocker under incomplete rather than consuming the remaining turn budget. Never weaken assertions or change acceptance criteria to make a test pass.',
@@ -234,7 +234,7 @@ function developerTail(verification) {
234
234
  lines.push('', '## Durable implementation progress', '', 'When loading the official apply skill, read its savedProgress handoff before repeating investigation. Reconcile it with the current diff and tasks; it is advisory history, never accepted verification evidence. Preserve completed work and previously discovered test commands or environment blockers. Recheck a blocker only when its relevant condition changed.', 'After each completed task, and whenever the next action or a blocker changes, call the scoped workflow action write_progress with progress: {summary, completedTasks, nextTasks, checks: [{command, outcome}], blockers}. Replace the previous handoff with a concise current account (at most 8 KB); include exact focused test commands, original exit outcomes, relevant paths and the next discriminating action. Keep nextTasks concrete enough for a fresh session to continue unfinished work. Do not include full logs, credentials or claims that old checks authorize acceptance. The host persists this record outside the frozen OpenSpec artifacts. Use read_progress to refresh it. If interrupted, the next session receives it from load_skill; do not write runtime files directly.', '', '## Output contract', '', 'Finish with exactly one JSON object and nothing after it: no prose after the object, no Markdown fence.', '', '```', '{"summary":"What you implemented and how","files":["src/feature.ts"],"tests":["src/feature.test.ts"],"verification":"npm test passed (12 tests)","incomplete":[{"task":"3. …","reason":"why it could not be completed"}],"blocker":{"kind":"toolchain","command":"npx playwright test","cwd":".","evidence":"exact error","requiredAction":"Run npx playwright install chromium"}}', '```', '', '- `files` and `tests`: repository-relative paths you created or modified (test files appear in `tests`, other files in `files`).', '- `verification`: the commands you ran and their outcome, or `none` when no shell was available.', '- `incomplete`: every task still `- [ ]` in `tasks.md`, with its reason; an empty array when everything is done.', '- `blocker`: only when the cause lies outside the change; omit otherwise. The host ends the run with your `requiredAction` instead of starting another correction round.', '- Do not paste full test logs; the reviewer reads the real verification evidence separately.', '');
235
235
  return lines;
236
236
  }
237
- function reviewerSection(policy, criteria, definition) {
237
+ function reviewerSection(policy, criteria, definition, adopted = false) {
238
238
  const aspects = REVIEW_ASPECTS.map(name => `\`${name}\` ≥ ${policy.aspects[name]}`).join(', ');
239
239
  const lines = definition === undefined ? [
240
240
  '## Your task: review',
@@ -246,7 +246,7 @@ function reviewerSection(policy, criteria, definition) {
246
246
  '1. Spec completeness: every requirement in the change specs and every acceptance criterion is implemented. Cross-reference each one against the code.',
247
247
  '2. Task completion: every task in `tasks.md` is `- [x]` and is backed by real code and tests, not just a ticked box.',
248
248
  '3. Test quality: new behavior has tests that assert on behavior, cover error paths, and would fail without the change. Missing tests for production code, tests without assertions and tests that restate the implementation are blocking issues.',
249
- '4. Blast radius: compare the change set below with the files design.md and tasks.md name. A file the plan did not name, a hunk no task explains (reformatting, import reordering, renames, comment rewrites, type widening, deleted or rewritten tests outside the tasks, regenerated lockfiles, snapshots or generated files, new dependencies, widened public signatures) or an edit to a shared module without a stated reason is an issue to REVERT, not to polish: name the file and the hunk. Trace every criterion to the narrowest code that satisfies it; code beyond that is suspect. Unjustified blast radius lowers `architectural_alignment` and `pattern_adherence`.',
249
+ '4. Blast radius: compare the change set below with the files design.md and tasks.md name. A file the plan did not name, a hunk no task explains (reformatting, import reordering, renames, comment rewrites, type widening, deleted or rewritten tests outside the tasks, regenerated lockfiles, snapshots or generated files' + (adopted ? ' other than the host-adopted verification output listed below' : '') + ', new dependencies, widened public signatures) or an edit to a shared module without a stated reason is an issue to REVERT, not to polish: name the file and the hunk. Trace every criterion to the narrowest code that satisfies it; code beyond that is suspect. Unjustified blast radius lowers `architectural_alignment` and `pattern_adherence`.',
250
250
  '5. Correctness and conventions: types and signatures fit the codebase, patterns match the repository, imports and error handling are consistent, no dead code, debug output or placeholders. Architecture: the change respects the dependency direction and boundaries the repository already follows; domain logic placed in an adapter, an adapter imported from the domain, a bypassed existing port or helper, a duplicated utility that already exists, or a new abstraction with one implementation and no variation point is an issue when it breaks a convention the code enforces and a finding otherwise. Platform correctness as far as the change touches it: web state, accessibility, loading and error states; mobile lifecycle, offline and permissions; backend transactions, idempotency and validation at the boundary.',
251
251
  '6. Security: no secrets, injection, path traversal, unsafe deserialization, missing authorization or new attack surface. Scale scrutiny to what the change touches.',
252
252
  '7. Performance: no obvious N+1, unbounded loops or blocking work on hot paths introduced by the change.',
@@ -298,6 +298,28 @@ function discardedSection(developer) {
298
298
  return [];
299
299
  return ['## Edits Core undid', '', 'The previous turn edited files outside the repository scope; Core restored them because they are not part of this change. Do not redo them:', ...developer.discarded.slice(0, 50).map(file => `- \`${file}\``), ''];
300
300
  }
301
+ /** Host-adopted verification output from the options and the verification feedback, deduplicated and bounded. */
302
+ function adoptedOutputs(feedback, extra) {
303
+ const raw = record(feedback?.verification)?.adoptedOutputs;
304
+ const listed = [...(extra ?? []), ...(Array.isArray(raw) ? raw : [])].map(record).filter((item) => typeof item?.path === 'string' && item.path.length > 0);
305
+ return [...new Map(listed.map(item => [String(item.repositoryId ?? '') + '\0' + String(item.path), { ...(typeof item.repositoryId === 'string' ? { repositoryId: item.repositoryId } : {}), path: String(item.path).slice(0, 512) }])).values()].slice(0, 50);
306
+ }
307
+ /** Files the verification commands regenerated themselves: reverting them only makes the next verification regenerate them. */
308
+ function adoptedOutputSection(role, adopted) {
309
+ if (!adopted.length || (role !== 'reviewer' && role !== 'developer'))
310
+ return [];
311
+ return [
312
+ '## Host-adopted verification output',
313
+ '',
314
+ 'The host verification commands regenerated these files themselves; the host kept them in the candidate and verified it again, so they belong to the change. They are exempt from the blast-radius rule on regenerated generated files:',
315
+ ...adopted.map(file => `- ${file.repositoryId ? '`' + file.repositoryId + '`: ' : ''}\`${file.path}\``),
316
+ '',
317
+ role === 'reviewer'
318
+ ? 'Never ask for one of them to be reverted to the base version. You may still raise an issue when an adopted file\'s content is wrong for this change.'
319
+ : 'Never revert them to the base version (no checkout, restore or deletion): the next verification would regenerate them. Edit one only when a reported failure lies in its content.',
320
+ '',
321
+ ];
322
+ }
301
323
  function feedbackSection(feedback, focused = false) {
302
324
  const lines = [];
303
325
  const verification = record(feedback?.verification);
@@ -399,8 +421,9 @@ export function roleInstructions(roleOrDescriptor, context, change, options = {}
399
421
  }
400
422
  const feedback = feedbackSection(options.feedback);
401
423
  const corrections = role === 'developer' && feedback.length > 0;
424
+ const adopted = role === 'architect' ? [] : adoptedOutputs(options.feedback, options.adoptedOutputs);
402
425
  const sections = [
403
- ...(role === 'architect' ? architectSection(options.verification, options.definition) : role === 'developer' ? (options.stance === 'fixer' ? fixerSection(options.verification, options.definition) : developerSection(options.verification, corrections, options.definition)) : reviewerSection(options.policy ?? DEFAULT_REVIEW_POLICY, options.criteria, options.definition)),
426
+ ...(role === 'architect' ? architectSection(options.verification, options.definition) : role === 'developer' ? (options.stance === 'fixer' ? fixerSection(options.verification, options.definition, adopted.length > 0) : developerSection(options.verification, corrections, options.definition, adopted.length > 0)) : reviewerSection(options.policy ?? DEFAULT_REVIEW_POLICY, options.criteria, options.definition, adopted.length > 0)),
404
427
  ...boundarySection(role),
405
428
  ...conventionsSection(),
406
429
  ...scopeSection(context, change),
@@ -410,6 +433,7 @@ export function roleInstructions(roleOrDescriptor, context, change, options = {}
410
433
  ...changeSetSection(role, options.stance, options.changeSet),
411
434
  ...(role === 'developer' ? discardedSection(options.developer) : []),
412
435
  ...(role === 'reviewer' ? reReviewSection(options.reReview) : []),
436
+ ...adoptedOutputSection(role, adopted),
413
437
  ...feedback,
414
438
  ];
415
439
  return sections.join('\n').trimEnd() + '\n';
@@ -421,7 +445,7 @@ export function correctionInstructions(role, feedback, extra = {}) {
421
445
  const instruction = extra.focusedEvidence
422
446
  ? `Continue the ${role} role with unchanged permissions and obligations. Fix the feedback, preserve correct work, finish all tasks and mark them \`- [x]\`. Return only the same JSON summary (summary, files, tests, verification, incomplete).`
423
447
  : 'Continue the same ' + role + ' role in this session. Address the feedback below precisely, keep the already-correct work, finish every remaining task, mark completed tasks `- [x]` in `tasks.md`, and finish with the same JSON summary object as before (summary, files, tests, verification, incomplete), with nothing after it.';
424
- const lines = [instruction, '', ...(role === 'developer' ? [...changeSetSection(role, undefined, extra.changeSet), ...discardedSection(extra.developer)] : []), ...feedbackSection(feedback, extra.focusedEvidence)];
448
+ const lines = [instruction, '', ...(role === 'developer' ? [...changeSetSection(role, undefined, extra.changeSet), ...discardedSection(extra.developer)] : []), ...adoptedOutputSection(role, adoptedOutputs(feedback, undefined)), ...feedbackSection(feedback, extra.focusedEvidence)];
425
449
  return lines.join('\n').trimEnd() + '\n';
426
450
  }
427
451
  /** Asks the architect, still in its session, to resolve a low-confidence design by investigating instead of guessing. */
@@ -9,37 +9,37 @@ export declare const recoveryRequestSchema: z.ZodDiscriminatedUnion<[z.ZodObject
9
9
  path: z.ZodString;
10
10
  action: z.ZodLiteral<"list_files">;
11
11
  }, z.core.$strict>, z.ZodObject<{
12
- startLine: z.ZodOptional<z.ZodNumber>;
13
- endLine: z.ZodOptional<z.ZodNumber>;
14
12
  repositoryId: z.ZodString;
15
13
  path: z.ZodString;
16
14
  action: z.ZodLiteral<"read_file">;
15
+ startLine: z.ZodOptional<z.ZodNumber>;
16
+ endLine: z.ZodOptional<z.ZodNumber>;
17
17
  }, z.core.$strict>, z.ZodObject<{
18
18
  repositoryId: z.ZodString;
19
19
  path: z.ZodString;
20
20
  action: z.ZodLiteral<"diff">;
21
21
  }, z.core.$strict>, z.ZodObject<{
22
- expectedHash: z.ZodString;
23
- oldText: z.ZodString;
24
- newText: z.ZodString;
22
+ repositoryId: z.ZodString;
23
+ path: z.ZodString;
25
24
  operationId: z.ZodString;
26
25
  reason: z.ZodString;
27
26
  acknowledgeInterrupted: z.ZodOptional<z.ZodBoolean>;
28
27
  changedPrecondition: z.ZodOptional<z.ZodString>;
29
- repositoryId: z.ZodString;
30
- path: z.ZodString;
31
28
  action: z.ZodLiteral<"patch">;
29
+ expectedHash: z.ZodString;
30
+ oldText: z.ZodString;
31
+ newText: z.ZodString;
32
32
  }, z.core.$strict>, z.ZodObject<{
33
- kind: z.ZodEnum<{
34
- verification: "verification";
35
- openspec: "openspec";
36
- }>;
37
- checkId: z.ZodOptional<z.ZodString>;
38
33
  operationId: z.ZodString;
39
34
  reason: z.ZodString;
40
35
  acknowledgeInterrupted: z.ZodOptional<z.ZodBoolean>;
41
36
  changedPrecondition: z.ZodOptional<z.ZodString>;
42
37
  action: z.ZodLiteral<"check">;
38
+ kind: z.ZodEnum<{
39
+ openspec: "openspec";
40
+ verification: "verification";
41
+ }>;
42
+ checkId: z.ZodOptional<z.ZodString>;
43
43
  }, z.core.$strict>], "action">;
44
44
  export type RecoveryRequest = z.infer<typeof recoveryRequestSchema>;
45
45
  /** Uses the SAME cross-process lease as resume. No provider calls or checkpoint editing. */
@@ -0,0 +1,176 @@
1
+ import type { SessionEventEnvelope, ToolActivity } from '../domain/events.js';
2
+ import { type SessionSnapshot } from '../domain/snapshot.js';
3
+ import { type SessionPolicy, type SubagentPhase, type Usage } from '../domain/types.js';
4
+ import { type RateCard } from '../domain/usage.js';
5
+ import type { Clock, CloseReason, DriverFactory, DriverInput, Ids, SessionJournal } from '../ports.js';
6
+ import { type OutputLimits } from './output-buffer.js';
7
+ export interface ActiveSessionDeps {
8
+ journal: SessionJournal;
9
+ factory: DriverFactory;
10
+ clock: Clock;
11
+ ids: Ids;
12
+ rateCard: (driver: string, model: string) => RateCard | null;
13
+ publish: (envelopes: SessionEventEnvelope[]) => void;
14
+ /** Called whenever the session's process liveness or recency changes (LRU bookkeeping). */
15
+ touched: (session: ActiveSession) => void;
16
+ outputLimits?: OutputLimits;
17
+ /** How long `interrupt` waits for the provider before ending the process. */
18
+ interruptGraceMs?: number;
19
+ /** Delay before buffered deltas are flushed. */
20
+ flushMs?: number;
21
+ }
22
+ export interface SessionConfig {
23
+ cwd: string;
24
+ model: string;
25
+ effort: string | null;
26
+ systemPrompt: string | null;
27
+ policy: SessionPolicy;
28
+ }
29
+ /** How work that was running ends when the process goes away. */
30
+ interface Teardown {
31
+ turnStatus: 'stopped' | 'interrupted' | 'failed';
32
+ subagentPhase: SubagentPhase;
33
+ reason: string;
34
+ error?: string;
35
+ }
36
+ /**
37
+ * Coordinates one session: turns driver facts into journaled events, owns the
38
+ * provider process lifecycle, phases, settlement, limits and teardown.
39
+ * All mutation goes through `commit`, which folds before appending, so the
40
+ * journal never holds an event the domain would reject.
41
+ */
42
+ export declare class ActiveSession {
43
+ readonly sessionId: string;
44
+ private config;
45
+ private readonly deps;
46
+ state: SessionSnapshot;
47
+ private driver;
48
+ private opening;
49
+ private generation;
50
+ /** Generation whose events are accepted (the live or opening process); null when none. */
51
+ private liveGeneration;
52
+ private closing;
53
+ private baseline;
54
+ private readonly timers;
55
+ private readonly output;
56
+ private readonly descriptor;
57
+ /** Inputs held back for drivers without a native input queue. */
58
+ private readonly heldInputs;
59
+ /** Inputs the application wrote on its own (policy-driven turns). */
60
+ private readonly systemInputIds;
61
+ private pendingDeferred;
62
+ private roster;
63
+ private handoffs;
64
+ private finishedSinceLastTurn;
65
+ /** Finished delegated sub-agents whose results the agent has not received yet. */
66
+ private readonly delegatedResults;
67
+ private lastUserTurnAt;
68
+ lastUsedAt: number;
69
+ constructor(sessionId: string, snapshot: SessionSnapshot, config: SessionConfig, deps: ActiveSessionDeps);
70
+ get processAlive(): boolean;
71
+ get isBusy(): boolean;
72
+ get policy(): SessionPolicy;
73
+ send(input: {
74
+ inputId: string;
75
+ text: string;
76
+ attachments?: DriverInput['attachments'];
77
+ delivery: 'queue' | 'steer';
78
+ }): Promise<{
79
+ inputId: string;
80
+ state: string;
81
+ }>;
82
+ interrupt(): Promise<string | null>;
83
+ stopSubagents(ids?: string[]): Promise<string[]>;
84
+ /**
85
+ * Policy says no sub-agents, yet the provider started one (a provider switch
86
+ * can be ignored by a model or CLI version). Stop it and say so: the policy
87
+ * holds whatever the provider's own switch does.
88
+ */
89
+ private blockSubagent;
90
+ /** Apply now when nothing is running; otherwise defer to the next idle point. */
91
+ update(changes: {
92
+ model?: string;
93
+ effort?: string;
94
+ systemPrompt?: string;
95
+ policy?: SessionPolicy;
96
+ }): Promise<'applied' | 'deferred'>;
97
+ close(reason: string): Promise<void>;
98
+ /**
99
+ * Record work a previous host left running as interrupted. Nothing is
100
+ * restarted: the next user input carries the interruption notice instead.
101
+ */
102
+ recoverAfterHostLoss(reason: 'restart' | 'host_lost'): void;
103
+ /** Retire the provider process; running work ends as described by `teardown`. */
104
+ retire(reason: CloseReason, teardown: Teardown): Promise<void>;
105
+ private deliver;
106
+ private ensureDriver;
107
+ private onDriverEvent;
108
+ private handleDriverEvent;
109
+ private startTurn;
110
+ private completeTurn;
111
+ /**
112
+ * Inputs whose receipts never reported completion are completed with their
113
+ * turn: the ones that opened it and the ones steered into it.
114
+ */
115
+ private completeTurnInputs;
116
+ private settlePhaseAfterTurn;
117
+ private afterSubagentChange;
118
+ private onSettled;
119
+ /** Live = running in our tree and, when the provider reports a roster, still on it. */
120
+ private liveCount;
121
+ private maybeResumeAgent;
122
+ /** Give the agent the results of delegated sub-agents it has not seen. */
123
+ private maybeDeliverDelegatedResults;
124
+ /** Running sub-agents Core launched for this session. */
125
+ delegatedLive(): string[];
126
+ delegatedStarted(child: {
127
+ subagentId: string;
128
+ description: string;
129
+ agentType: string;
130
+ prompt: string;
131
+ driver: string;
132
+ model: string;
133
+ }): void;
134
+ delegatedOutput(subagentId: string, event: {
135
+ channel: 'text';
136
+ delta: string;
137
+ } | {
138
+ channel: 'tool';
139
+ tool: ToolActivity;
140
+ }): void;
141
+ /**
142
+ * A delegated child ended. Its usage is its own provider's spend (billed
143
+ * separately); unless a host already collected the result, the agent gets it
144
+ * when the session next settles.
145
+ */
146
+ delegatedFinished(subagentId: string, outcome: {
147
+ status: 'completed' | 'failed' | 'stopped' | 'interrupted';
148
+ text: string;
149
+ usage: Usage | null;
150
+ reason?: string;
151
+ collected: boolean;
152
+ }): void;
153
+ /** A host received these results through a wait; never inject them again. */
154
+ collectDelegated(subagentIds: readonly string[]): void;
155
+ private enterIdle;
156
+ private enterBackground;
157
+ private idleTeardown;
158
+ private armTurnInactivity;
159
+ private markActivity;
160
+ private applyChanges;
161
+ private releaseHeldInput;
162
+ /** End whatever was running; used by retire and unexpected exits. */
163
+ private endRunningWork;
164
+ private turnUsage;
165
+ private subagentUsage;
166
+ private commitBuffered;
167
+ /**
168
+ * Validate (fold) then append atomically, then publish. Buffered deltas are
169
+ * flushed first so ordering is preserved.
170
+ */
171
+ private commit;
172
+ private requireTurn;
173
+ private assertOpen;
174
+ private touch;
175
+ }
176
+ export {};