sortie-dogs 0.10.9 → 0.10.11

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.
@@ -3,5 +3,5 @@
3
3
  * installed project marker without importing every asset body.
4
4
  */
5
5
  export declare const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
6
- export declare const V010_RUNTIME_ASSET_VERSION = "0.10.0-v0912-language4-cost-rpt10-compaction-ref2-proposal1-review-remediation1-surface4-proposal-recovery2-quality1-terminal2-route1-path1-permission2-prerequisite1-v2-bridge1-intent1";
6
+ export declare const V010_RUNTIME_ASSET_VERSION = "0.10.0-v0912-language4-cost-rpt10-compaction-ref2-proposal1-review-remediation1-surface4-proposal-recovery2-quality1-terminal2-route1-path1-permission2-prerequisite1-v2-bridge1-intent1-reflection1-cancel1-proposal-budget1-goal-recovery1";
7
7
  export type RuntimeAssetVersion = typeof RUNTIME_ASSET_VERSION | typeof V010_RUNTIME_ASSET_VERSION;
@@ -3,4 +3,4 @@
3
3
  * installed project marker without importing every asset body.
4
4
  */
5
5
  export const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
6
- export const V010_RUNTIME_ASSET_VERSION = "0.10.0-v0912-language4-cost-rpt10-compaction-ref2-proposal1-review-remediation1-surface4-proposal-recovery2-quality1-terminal2-route1-path1-permission2-prerequisite1-v2-bridge1-intent1";
6
+ export const V010_RUNTIME_ASSET_VERSION = "0.10.0-v0912-language4-cost-rpt10-compaction-ref2-proposal1-review-remediation1-surface4-proposal-recovery2-quality1-terminal2-route1-path1-permission2-prerequisite1-v2-bridge1-intent1-reflection1-cancel1-proposal-budget1-goal-recovery1";
@@ -86,6 +86,24 @@ export interface OperatorProposalState {
86
86
  proposal: OperatorProposalPacket | null;
87
87
  approval_rationale: string | null;
88
88
  }
89
+ export interface OperatorProposalBudgetDiagnostic {
90
+ readonly status: "proposal-budget-exhausted";
91
+ readonly code: "operator-proposal-read-budget-exhausted" | "operator-proposal-submission-budget-exhausted";
92
+ readonly reads: number;
93
+ readonly max_reads: number;
94
+ readonly remaining_reads: number;
95
+ readonly submissions: number;
96
+ readonly max_submissions: number;
97
+ readonly remaining_submissions: number;
98
+ readonly required_minimum_budget: {
99
+ readonly max_reads: number;
100
+ readonly max_submissions: number;
101
+ };
102
+ }
103
+ export declare class OperatorProposalBudgetError extends Error {
104
+ readonly diagnostic: OperatorProposalBudgetDiagnostic;
105
+ constructor(diagnostic: OperatorProposalBudgetDiagnostic);
106
+ }
89
107
  /** Durable pre-execution lane. It grants investigation and submission, never source mutation or execution. */
90
108
  export declare class OperatorProposalRuntime {
91
109
  readonly profile: RuntimeProfile;
@@ -101,6 +119,7 @@ export declare class OperatorProposalRuntime {
101
119
  read(root: string): Promise<OperatorProposalState | undefined>;
102
120
  private readUnlocked;
103
121
  private save;
122
+ private assertBudgetAvailable;
104
123
  task(state: OperatorProposalState): OperatorTask;
105
124
  /** Root-visible bounded handle; the canonical prompt remains only in durable host state. */
106
125
  referenceTask(state: OperatorProposalState): OperatorTask;
@@ -93,6 +93,13 @@ const normalizedRelativePath = (value) => {
93
93
  return false;
94
94
  }
95
95
  };
96
+ export class OperatorProposalBudgetError extends Error {
97
+ diagnostic;
98
+ constructor(diagnostic) {
99
+ super(diagnostic.code);
100
+ this.diagnostic = diagnostic;
101
+ }
102
+ }
96
103
  function parseIntent(value) {
97
104
  const proposalBudget = record(value) && Object.hasOwn(value, "proposal_budget")
98
105
  ? value.proposal_budget
@@ -366,6 +373,18 @@ export class OperatorProposalRuntime {
366
373
  }
367
374
  this.states.set(state.root_session_id, structuredClone(state));
368
375
  }
376
+ assertBudgetAvailable(state) {
377
+ const remainingReads = state.intent.proposal_budget.max_reads - state.read_count;
378
+ const remainingSubmissions = state.intent.proposal_budget.max_submissions - state.submission_count;
379
+ if (remainingReads > 0 && remainingSubmissions > 0)
380
+ return;
381
+ throw new OperatorProposalBudgetError({ status: "proposal-budget-exhausted",
382
+ code: remainingReads <= 0 ? "operator-proposal-read-budget-exhausted" : "operator-proposal-submission-budget-exhausted",
383
+ reads: state.read_count, max_reads: state.intent.proposal_budget.max_reads, remaining_reads: Math.max(0, remainingReads),
384
+ submissions: state.submission_count, max_submissions: state.intent.proposal_budget.max_submissions,
385
+ remaining_submissions: Math.max(0, remainingSubmissions),
386
+ required_minimum_budget: { max_reads: state.read_count + 1, max_submissions: state.submission_count + 1 } });
387
+ }
369
388
  task(state) {
370
389
  return { subagent_type: this.profile.agentNames["dog-operator"], description: "承認前の調査と実行契約案作成",
371
390
  prompt: [`SORTIE_OPERATOR_PROPOSAL ${JSON.stringify({ root: state.root_session_id, intent_id: state.intent_id, intent_hash: state.intent_hash })}`,
@@ -435,8 +454,10 @@ export class OperatorProposalRuntime {
435
454
  async beginUnlocked(root, raw) {
436
455
  const intent = parseIntent(raw), intentHash = hash(JSON.stringify(intent)), previous = await this.readUnlocked(root);
437
456
  if (previous) {
438
- if (previous.intent_hash === intentHash)
457
+ if (previous.intent_hash === intentHash) {
458
+ this.assertBudgetAvailable(previous);
439
459
  return previous;
460
+ }
440
461
  if (previous.phase !== "approved")
441
462
  throw new Error("operator-proposal-active-intent-immutable");
442
463
  throw new Error("operator-proposal-new-intent-requires-explicit-root-revision");
@@ -446,6 +467,7 @@ export class OperatorProposalRuntime {
446
467
  intent_id: `intent-${intentHash.slice(0, 24)}`, intent_hash: intentHash, intent, created_at: new Date().toISOString(), phase: "investigating",
447
468
  goal_binding: null, proposal_call_id: null, proposal_session_id: null, read_count: spend.reads, read_paths: [], submission_count: spend.submissions, proposal_id: null,
448
469
  proposal_revision: null, proposal_hash: null, proposal: null, approval_rationale: null };
470
+ this.assertBudgetAvailable(state);
449
471
  await this.save(state);
450
472
  return state;
451
473
  }
@@ -474,6 +496,7 @@ export class OperatorProposalRuntime {
474
496
  const state = await this.requiredUnlocked(root);
475
497
  if (state.phase !== "investigating" || state.proposal_call_id !== null)
476
498
  throw new Error("operator-proposal-dispatch-not-authorized");
499
+ this.assertBudgetAvailable(state);
477
500
  const canonical = this.resolveTask(state, args);
478
501
  // Reserve only after the grant check, while competing admissions remain queued.
479
502
  await reserveBudget?.();
@@ -33,7 +33,7 @@ export const DEFAULT_PLUGIN_OPTIONS = {
33
33
  capability: CONTINUATION_CAPABILITY,
34
34
  taskWatchdogMilliseconds: DEFAULT_TASK_WATCHDOG_MILLISECONDS,
35
35
  }),
36
- reflection: Object.freeze({ enabled: false, layers: Object.freeze({ run: true, project: true, global: false }), maxInjectedEntries: 3, maxInjectedTokens: 500 }),
36
+ reflection: Object.freeze({ enabled: true, layers: Object.freeze({ run: true, project: true, global: true }), maxInjectedEntries: 3, maxInjectedTokens: 500 }),
37
37
  validationProfile: "balanced",
38
38
  };
39
39
  function isRecord(value) {
@@ -7573,6 +7573,10 @@ export const SortieDogsPlugin = async (input, options) => {
7573
7573
  registerGoalDeclaration: async (root, prompt) => {
7574
7574
  if (!isCoordinatorSession(root) && !await recoverCoordinatorRoot(root))
7575
7575
  throw new Error("operator-coordinator-required");
7576
+ // In one-shot CLI turns OpenCode may persist the user message only after chat.message returns.
7577
+ // Proposal preparation can reach registration first, so recover that exact pending real turn here
7578
+ // instead of freezing a prepared contract that can never consume its user revision authority.
7579
+ await recoverPendingRealGoalTurn(root);
7576
7580
  const registered = await bindGoalDeclaration(root, prompt);
7577
7581
  if (registered?.goal_id === null || registered === undefined)
7578
7582
  throw new Error("operator-goal-registration-unavailable");
@@ -7588,6 +7592,7 @@ export const SortieDogsPlugin = async (input, options) => {
7588
7592
  relinkRegisteredGoal: async (root, request) => {
7589
7593
  if (!isCoordinatorSession(root) && !await recoverCoordinatorRoot(root))
7590
7594
  throw new Error("operator-coordinator-required");
7595
+ await recoverPendingRealGoalTurn(root);
7591
7596
  await recoverCompletedGoalReservations(root);
7592
7597
  const ledger = await goalLedger(root), snapshot = await ledger.readGoal(), state = snapshot.state;
7593
7598
  if (state.phase !== "active" || state.receipt !== null)
@@ -1,6 +1,6 @@
1
1
  import { V010_RUNTIME_ASSET_VERSION } from "../asset-version.js";
2
2
  import { OperatorContractError, OperatorRuntime } from "../core/operator-runtime.js";
3
- import { DEFAULT_OPERATOR_PROPOSAL_BUDGET, OPERATOR_APPROVAL_CONTRACT, OPERATOR_PROPOSAL_BUDGET_CAPS, OperatorProposalRuntime } from "../core/operator-proposal.js";
3
+ import { DEFAULT_OPERATOR_PROPOSAL_BUDGET, OPERATOR_APPROVAL_CONTRACT, OPERATOR_PROPOSAL_BUDGET_CAPS, OperatorProposalBudgetError, OperatorProposalRuntime } from "../core/operator-proposal.js";
4
4
  import { CANONICAL_AGENT_ROLES, canonicalAgent, profileAgent, profileTool, V010_RUNTIME_PROFILE } from "../core/runtime-profile.js";
5
5
  import { SortieDogsPlugin as canonicalPlugin } from "./index.js";
6
6
  import { taskChildSessionID } from "./task-result-repair.js";
@@ -40,7 +40,7 @@ const PREVIEW_ROUTES = Object.freeze([
40
40
  ]);
41
41
  export function processRemediationReplacementPacket(code, packet, cancelTool, prepareTool) {
42
42
  return { status: "operator-process-remediation-replacement-required", code, packet,
43
- next_action: `Call ${cancelTool} without a reason, then call ${prepareTool} with the exact same ordered acceptance, ` +
43
+ next_action: `Call ${cancelTool} with reason=plain, then call ${prepareTool} with the exact same ordered acceptance, ` +
44
44
  "the same unit and write scope, and the remaining cumulative budget. Do not call resume_operator again, " +
45
45
  "claim evidence, reset spend, or widen scope." };
46
46
  }
@@ -546,7 +546,7 @@ export function createProfiledPlugin(profile, assetVersion) {
546
546
  const proposal = await proposals.read(context.sessionID);
547
547
  const proposalNextAction = proposal?.phase === "investigating"
548
548
  ? proposal.proposal_call_id !== null
549
- ? "proposal Task is already admitted; do not redispatch it or call operator_next. Continue submission repairs only in the same active claimed child. If that child terminated without submission, report the terminal proposal failure; remaining read or submission capacity does not authorize a new Task, budget reset, or replacement child. Only an explicit root decision to retry may call cancel_operator with no reason to release this grant, which discards the spent proposal accounting and never reuses the terminated child"
549
+ ? "proposal Task is already admitted; do not redispatch it or call operator_next. Continue submission repairs only in the same active claimed child. If that child terminated without submission, report the terminal proposal failure; remaining read or submission capacity does not authorize a new Task, budget reset, or replacement child. Only an explicit root decision to retry may call cancel_operator with reason=plain to release this grant, which discards the spent proposal accounting and never reuses the terminated child"
550
550
  : proposal.submission_count >= proposal.intent.proposal_budget.max_submissions
551
551
  ? "proposal submission budget exhausted; do not call operator_next; report the bounded proposal failure"
552
552
  : "proposal is not submitted; do not call operator_next; complete or repair the bounded proposal submission"
@@ -562,10 +562,11 @@ export function createProfiledPlugin(profile, assetVersion) {
562
562
  next_action: proposalNextAction }
563
563
  : { status: "absent", profile: profile.id });
564
564
  } };
565
- tools[cancel] = { description: "Revoke this root's operator grant and stop only its owned children before releasing core state. Before approval it instead releases the bounded proposal grant, including one whose admitted child already terminated, so the root can begin a new investigation; it never reuses that child or restores spent proposal budget. reason is a closed set: omit it for a plain cancellation, including replanning, contract revision, and any pre-approval proposal release, and never send free text such as a written justification. Use reason=acceptance-remediation only for decision=operator-acceptance-remediation-required. At awaiting-acceptance, reason=review-blocking authorizes a same-goal review-remediation replacement only within the exact acceptance, committed head, approved write union, and retained remaining budget.",
566
- args: { reason: optionalStringSchema }, execute: async (args, context) => {
565
+ tools[cancel] = { description: "Revoke this root's operator grant and stop only its owned children before releasing core state. Before approval it instead releases the bounded proposal grant, including one whose admitted child already terminated, so the root can begin a new investigation; it never reuses that child or restores spent proposal budget. reason is a required closed set: use reason=plain for a plain cancellation, including replanning, contract revision, and any pre-approval proposal release, and never send free text such as a written justification. Use reason=acceptance-remediation only for decision=operator-acceptance-remediation-required. At awaiting-acceptance, reason=review-blocking authorizes a same-goal review-remediation replacement only within the exact acceptance, committed head, approved write union, and retained remaining budget.",
566
+ args: { reason: { type: "string", enum: ["plain", "review-blocking", "acceptance-remediation"] } }, execute: async (args, context) => {
567
567
  await requireRoot(context.sessionID);
568
- const requestedReason = args.reason;
568
+ const rawReason = args.reason;
569
+ const requestedReason = rawReason === "plain" || rawReason === undefined ? undefined : rawReason;
569
570
  if (requestedReason !== undefined && requestedReason !== "review-blocking" && requestedReason !== "acceptance-remediation")
570
571
  throw new Error("operator-cancel-reason-invalid");
571
572
  const pending = await operators.read(context.sessionID);
@@ -759,7 +760,15 @@ export function createProfiledPlugin(profile, assetVersion) {
759
760
  tools[beginProposal] = { description: `Root-only: freeze original ordered requirements and grant one bounded read-only proposal investigation to dogs-coordinator. The result contains one exact short Task reference; pass task.subagent_type, task.description, and task.prompt byte-for-byte without appending or paraphrasing. The native parent retains that reference; the host expands it only inside the directly claimed proposal child. ${OPERATOR_INTENT_CONTRACT}`,
760
761
  args: { intent_json: intentSchema }, execute: async (args, context) => {
761
762
  await requireRoot(context.sessionID);
762
- let state = await proposals.begin(context.sessionID, JSON.parse(args.intent_json));
763
+ let state;
764
+ try {
765
+ state = await proposals.begin(context.sessionID, JSON.parse(args.intent_json));
766
+ }
767
+ catch (error) {
768
+ if (error instanceof OperatorProposalBudgetError)
769
+ return JSON.stringify(error.diagnostic);
770
+ throw error;
771
+ }
763
772
  state = await proposals.bindGoal(context.sessionID, await control.proposalGoalBinding(context.sessionID));
764
773
  return JSON.stringify({ ...proposals.packet(state), task: proposals.referenceTask(state),
765
774
  dispatch_instruction: "Pass this short Task reference verbatim; do not append or paraphrase it." });
@@ -157,7 +157,7 @@ budgets or your own reporting obligations into worker validation commands. Prese
157
157
  That child remains the same logical operations role: it may inspect only the host-approved read prefixes and submit one
158
158
  requirement-mapped proposal; it cannot edit, use shell, dispatch a worker/advisor/scout, widen read scope, or execute work.
159
159
  An admitted proposal child is never redispatched or replaced. If it terminates without a submitted proposal, report that
160
- terminal failure first; only an explicit decision to retry may call ${profile.toolPrefix}cancel_operator with no reason to
160
+ terminal failure first; only an explicit decision to retry may call ${profile.toolPrefix}cancel_operator with reason=plain to
161
161
  release the grant before freezing the requirements again. Spent proposal reads and submissions are never restored.
162
162
  Do not cancel/reinvestigate the same known contract defect merely to get another draft. Preserve exact observed
163
163
  commands and correction evidence. If a user-only acceptance condition cannot be expressed by the existing contract,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sortie-dogs",
3
- "version": "0.10.9",
3
+ "version": "0.10.11",
4
4
  "description": "Bounded agent harness and validated orchestration loop plugin for OpenCode",
5
5
  "keywords": [
6
6
  "opencode",