sortie-dogs 0.10.14 → 0.10.15
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.
|
@@ -73,6 +73,11 @@ export interface OperatorProposalState {
|
|
|
73
73
|
readonly intent: OperatorIntent;
|
|
74
74
|
goal_binding: OperatorProposalGoalBinding | null;
|
|
75
75
|
readonly created_at: string;
|
|
76
|
+
/** Frozen carry-in spend; unlike live counters it must not change the admitted Task identity. */
|
|
77
|
+
readonly prior_spend?: {
|
|
78
|
+
readonly reads: number;
|
|
79
|
+
readonly submissions: number;
|
|
80
|
+
};
|
|
76
81
|
phase: "investigating" | "submitted" | "approved";
|
|
77
82
|
proposal_call_id: string | null;
|
|
78
83
|
proposal_session_id: string | null;
|
|
@@ -101,6 +101,25 @@ export class OperatorProposalBudgetError extends Error {
|
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
function parseIntent(value) {
|
|
104
|
+
if (record(value) && Array.isArray(value.allow_read)) {
|
|
105
|
+
const diagnostics = [];
|
|
106
|
+
value.allow_read.forEach((path, index) => {
|
|
107
|
+
if (normalizedRelativePath(path))
|
|
108
|
+
return;
|
|
109
|
+
let expected = "existing repository-relative file or directory; external absolute paths are not proposal read scopes";
|
|
110
|
+
if (typeof path === "string") {
|
|
111
|
+
try {
|
|
112
|
+
expected = normalizeRelativePath(path);
|
|
113
|
+
}
|
|
114
|
+
catch { /* Never authorize an external or traversal path. */ }
|
|
115
|
+
}
|
|
116
|
+
diagnostics.push({ document: "proposal", pointer: `/allow_read/${index}`, code: "operator-intent-read-path-invalid",
|
|
117
|
+
rule: "normalized-relative-path", repair_kind: "repair-field", repair_paths: [`/allow_read/${index}`],
|
|
118
|
+
expected, actual_type: jsonType(path) });
|
|
119
|
+
});
|
|
120
|
+
if (diagnostics.length)
|
|
121
|
+
throw new OperatorContractError(diagnostics);
|
|
122
|
+
}
|
|
104
123
|
const proposalBudget = record(value) && Object.hasOwn(value, "proposal_budget")
|
|
105
124
|
? value.proposal_budget
|
|
106
125
|
: DEFAULT_OPERATOR_PROPOSAL_BUDGET;
|
|
@@ -392,6 +411,11 @@ export class OperatorProposalRuntime {
|
|
|
392
411
|
`original_request: ${state.intent.original_request.text}`, "ordered_requirements:", ...state.intent.requirements.map(item => `- ${item.id} [${item.kind}]: ${item.text}`),
|
|
393
412
|
`authoritative_refs: ${JSON.stringify(state.intent.authoritative_refs)}`, `allow_read: ${JSON.stringify(state.intent.allow_read)}`,
|
|
394
413
|
`proposal_budget: ${JSON.stringify(state.intent.proposal_budget)}`,
|
|
414
|
+
"要件に相談や利用者判断が含まれる場合、workerから追加Taskを呼ぶ計画にしない。rootが派遣前に必要な相談を行い、確認済みの素材許諾・選択・画像参照・決定をunit.objectiveと許可された入力へ明記する。子はroot会話を自動継承しない。未確定の判断だけを未確定として保持し、確認済み事項を再質問させない。実機測定や利用者Goの未達をfixtureで代替しない。",
|
|
415
|
+
...(state.prior_spend ? [`prior_proposal_spend: ${JSON.stringify(state.prior_spend)}; remaining_at_start: ${JSON.stringify({
|
|
416
|
+
reads: state.intent.proposal_budget.max_reads - state.prior_spend.reads,
|
|
417
|
+
submissions: state.intent.proposal_budget.max_submissions - state.prior_spend.submissions
|
|
418
|
+
})}. These are cumulative limits, not fresh grants. budget_estimate.proposal_reads must include prior_proposal_spend.reads plus reads charged in this investigation. Prior read paths are not evidence for this new child.`] : []),
|
|
395
419
|
"proposal packet fields: schema_version, revision, coverage[{requirement_id,approach,validation}], existing_surface[{requirement_id,path,form}], uncovered[{requirement_id,reason}], negative_handling[{requirement_id,handling}], read_scope, write_scope, budget_estimate{proposal_reads,execution_units}, plan{schema_version,acceptance_proof,source_refs,goal_declaration,units, optional git_lifecycle}. revisionは正のJSON整数。例: \"revision\":1。文字列\"revision\":\"1\"は禁止し、hostは型変換しない。plan.acceptanceは生成・提出せず省略し、hostがdurable intent.requirementsのtextを同順・完全一致でmaterializeする。legacy互換として明示する場合だけ同じ全文・順序を完全一致で使い、null・部分集合・並べ替え・空白/大小文字変更は禁止。acceptance_ids等の代替field禁止。",
|
|
396
420
|
"schema_versionはpacketとplanの両方で必ず文字列\"0.1\"。coverageはnegative/qualityを含む全ordered requirement IDを各1回含める(未対応だけuncoveredへ移す)。全declared criterion IDをacceptance_proofから参照し、各unitのacceptance_indicesはnonempty、全acceptance indexとexact validation commandを既存parser規則どおり完全coverageする。",
|
|
397
421
|
"plan.acceptance_proofは各acceptanceに対応するcriterion IDの配列を並べた二重配列。複数acceptanceを同じexact commandが証明する場合はcriterion IDを共有可で、acceptanceごとの固有criterion作成は不要。goal_declarationはobject。delivery_intent=\"implementation\", delivery_mode=\"mvp-first\", usable_path_established=false, controlled_change=false, goal_budget_unitsは正整数。criteria各要素はcriterion_idとvalidation_commandを持つ。",
|
|
@@ -466,6 +490,7 @@ export class OperatorProposalRuntime {
|
|
|
466
490
|
const state = { schema_version: "0.1", profile: this.profile.id, root_session_id: root,
|
|
467
491
|
intent_id: `intent-${intentHash.slice(0, 24)}`, intent_hash: intentHash, intent, created_at: new Date().toISOString(), phase: "investigating",
|
|
468
492
|
goal_binding: null, proposal_call_id: null, proposal_session_id: null, read_count: spend.reads, read_paths: [], submission_count: spend.submissions, proposal_id: null,
|
|
493
|
+
...(spend.reads > 0 || spend.submissions > 0 ? { prior_spend: { reads: spend.reads, submissions: spend.submissions } } : {}),
|
|
469
494
|
proposal_revision: null, proposal_hash: null, proposal: null, approval_rationale: null };
|
|
470
495
|
this.assertBudgetAvailable(state);
|
|
471
496
|
await this.save(state);
|
|
@@ -797,6 +797,7 @@ export class OperatorRuntime {
|
|
|
797
797
|
"validation:", ...unit.validation.map(value => ` - ${value}`),
|
|
798
798
|
"Execute validation in its declared order. Earlier entries may be approved generator, build, formatter, or exact cleanup commands required before canonical criterion tests. Every persistent or transient generator output must be declared in unit.write. Cleanup may remove only declared unit.write outputs and must be an explicit ordered command after generation and before post-commit or canonical validation; never add an ignore rule or remove an undeclared path. If any necessary command, input, output, or cleanup is missing, do not run an undeclared command or variant and do not use resume evidence tooling to invent permission; return a contract-repair decision.",
|
|
799
799
|
"Preserve existing public API success and error return semantics unless acceptance explicitly changes them, and cover those compatibility boundaries in the declared validation.",
|
|
800
|
+
"Do not spawn nested subagents for consultation. Required consultations belong to the root before dispatch; use the confirmed decisions and evidence declared in the unit objective and inputs. If required consultation results or user decisions are missing, return the exact contract gap to the parent instead of attempting a deeper Task, inventing consent, or asking the user to repeat an already recorded decision.",
|
|
800
801
|
...(plan.git_lifecycle !== undefined && index === plan.units.length - 1 ? [
|
|
801
802
|
`git_post_commit_validation: ${JSON.stringify(plan.git_lifecycle.post_commit_validation)}`,
|
|
802
803
|
"Complete every source write before invoking any git_post_commit_validation command. Its first exact invocation is the host commit boundary; after it, source mutation and undeclared shell commands are denied. Run every listed command and return its real evidence.",
|
package/dist/plugin/profiled.js
CHANGED
|
@@ -449,6 +449,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
449
449
|
return JSON.stringify(operators.packet(state));
|
|
450
450
|
control.enableUnits(state.rootSessionID, state.units.length);
|
|
451
451
|
return JSON.stringify({ profile: profile.id, run_id: state.runID, acceptance_fingerprint: state.acceptanceFingerprint,
|
|
452
|
+
dispatch_instruction: "Dispatch this exact Task in the foreground. Omit background or set it to false; background=true is not supported by the operator lifecycle. Required consultations belong to the root before dispatch; confirmed user decisions must already be in the approved unit objective and inputs, not appended to this Task reference.",
|
|
452
453
|
fast_path: state.units.length === 1, task: state.units.length === 1 ? operators.nextWorkerTask(state) : operators.dispatchTask(state) });
|
|
453
454
|
}
|
|
454
455
|
tools[prepare] = { description: `Freeze an approved serial operator plan. Invalid plans return bounded diagnostics and a draft_id for field-only repair; no worker is started. After operator-acceptance-remediation-required, cancel first and prepare only the same exact acceptance, a write scope within the prior approved union, and a Git lifecycle starting at the failed committed head; consumed goal budget is retained. Coordinator only. ${planContract}`,
|
|
@@ -549,17 +550,18 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
549
550
|
const proposal = await proposals.read(context.sessionID);
|
|
550
551
|
const proposalNextAction = proposal?.phase === "investigating"
|
|
551
552
|
? proposal.proposal_call_id !== null
|
|
552
|
-
? "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,
|
|
553
|
+
? "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 or was interrupted without submission, an explicit root decision to retry may call cancel_operator with reason=plain to release this grant. Cancellation stops owned children and durably preserves cumulative reads/submissions and goal spend. Pass the returned retry_intent unchanged to begin_operator_proposal, then dispatch only its new exact Task within the remaining budget. Never reuse the terminated child, reset spend, or replace the goal or ordered requirements. Exhaustion requires an explicit cumulative budget revision, not a reset."
|
|
553
554
|
: proposal.submission_count >= proposal.intent.proposal_budget.max_submissions
|
|
554
555
|
? "proposal submission budget exhausted; do not call operator_next; report the bounded proposal failure"
|
|
555
556
|
: "proposal is not submitted; do not call operator_next; complete or repair the bounded proposal submission"
|
|
556
557
|
: proposal?.phase === "submitted"
|
|
557
|
-
? "compare and approve the exact proposal; do not call operator_next before approval prepares a run"
|
|
558
|
+
? "compare and approve the exact proposal; do not call operator_next before approval prepares a run. Approval authorizes implementation, not product acceptance. Preserve every required live measurement, consultation and user approval as pending acceptance obligations; canonical commands do not substitute for them. Use the host budget counters, not plan.goal_declaration.goal_budget_units, for remaining capacity; approval validates the retained goal binding and execution budget. A bounded diagnostic run may use cancel_operator after admission without inventing a plan validation command for cancellation."
|
|
558
559
|
: "approved proposal has no operator run; do not call operator_next; reconcile the approval or preparation failure";
|
|
559
560
|
return JSON.stringify(state ? { ...await operatorPacket(state), ...(draft ? { pending_draft: draft } : {}),
|
|
560
561
|
...(proposal ? { proposal: proposalIdentity(proposal) } : {}) }
|
|
561
562
|
: draft ? { ...draft, ...(proposal ? { proposal: proposals.packet(proposal) } : {}) }
|
|
562
563
|
: proposal ? { profile: profile.id, proposal: proposals.packet(proposal),
|
|
564
|
+
goal_binding: proposal.goal_binding, budget: await control.currentBudget(context.sessionID),
|
|
563
565
|
...(proposal.phase === "investigating" && proposal.proposal_call_id === null ? { task: proposals.referenceTask(proposal),
|
|
564
566
|
dispatch_instruction: "Pass this short Task reference verbatim; the host expands it only inside the directly claimed proposal child." } : {}),
|
|
565
567
|
next_action: proposalNextAction }
|
|
@@ -587,8 +589,11 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
587
589
|
operatorTurnLifecycle.set(context.sessionID, "cancelled");
|
|
588
590
|
return JSON.stringify({ profile: profile.id, status: "cancelled", scope: "proposal",
|
|
589
591
|
released_proposal: discarded ? proposals.packet(discarded) : null,
|
|
592
|
+
retry_intent: discarded?.intent ?? null,
|
|
593
|
+
retained_goal_binding: discarded?.goal_binding ?? null,
|
|
590
594
|
next_action: "the bounded proposal grant is released and its spent reads/submissions are not restored; " +
|
|
591
|
-
"
|
|
595
|
+
"cumulative proposal and goal spend are preserved. On an explicit root decision to retry, pass retry_intent unchanged to begin_operator_proposal, " +
|
|
596
|
+
"preserve the same goal and ordered requirements, and dispatch only the new exact Task within the remaining budget. Never reuse the cancelled child or reset any budget" });
|
|
592
597
|
}
|
|
593
598
|
const current = pending;
|
|
594
599
|
if (requestedReason === "acceptance-remediation" && current.decision !== "operator-acceptance-remediation-required")
|
|
@@ -772,6 +777,9 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
772
777
|
catch (error) {
|
|
773
778
|
if (error instanceof OperatorProposalBudgetError)
|
|
774
779
|
return JSON.stringify(error.diagnostic);
|
|
780
|
+
if (error instanceof OperatorContractError)
|
|
781
|
+
return JSON.stringify({ status: "invalid-intent",
|
|
782
|
+
diagnostics: error.diagnostics, next_action: "Repair only the diagnosed fields and retry begin_operator_proposal; preserve original_request and every ordered requirement. No proposal was admitted and no budget was spent." });
|
|
775
783
|
throw error;
|
|
776
784
|
}
|
|
777
785
|
state = await proposals.bindGoal(context.sessionID, await control.proposalGoalBinding(context.sessionID));
|
package/dist/plugin/v2.js
CHANGED
|
@@ -136,6 +136,11 @@ function legacyToolName(name) {
|
|
|
136
136
|
function legacyToolInput(name, input) {
|
|
137
137
|
const value = record(input) ? { ...input } : {};
|
|
138
138
|
if (name === "subagent") {
|
|
139
|
+
if (value.background === true && typeof value.prompt === "string" && value.prompt.startsWith("SORTIE_OPERATOR_")) {
|
|
140
|
+
throw new Error("operator-background-dispatch-not-supported: retry the same exact Task with background omitted or false; no admission or budget reservation occurred. Operator dispatch requires foreground completion for lifecycle accounting.");
|
|
141
|
+
}
|
|
142
|
+
if (value.background === false)
|
|
143
|
+
delete value.background;
|
|
139
144
|
if (typeof value.agent === "string")
|
|
140
145
|
value.subagent_type = value.agent;
|
|
141
146
|
if (typeof value.sessionID === "string")
|