sortie-dogs 0.10.8 → 0.10.10

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";
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";
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";
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";
@@ -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) {
@@ -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);
@@ -1088,6 +1089,11 @@ export function createProfiledPlugin(profile, assetVersion) {
1088
1089
  const proposal = who.role === "dog-operator" ? await proposals.read(root) : undefined;
1089
1090
  const proposalChild = proposal?.phase === "investigating" && proposal.proposal_session_id === request.sessionID;
1090
1091
  if (proposalChild) {
1092
+ // OpenCode V2 exposes server plugin tools to Code Mode through its `execute`
1093
+ // conduit. The nested submit tool still performs the session/grant checks below;
1094
+ // denying the conduit makes an otherwise read-only proposal impossible to submit.
1095
+ if (request.tool === "execute")
1096
+ return;
1091
1097
  if (request.tool !== "read")
1092
1098
  throw new Error("operator-proposal-readonly-role");
1093
1099
  if (typeof args.filePath !== "string")
package/dist/plugin/v2.js CHANGED
@@ -140,6 +140,8 @@ function legacyToolInput(name, input) {
140
140
  value.subagent_type = value.agent;
141
141
  if (typeof value.sessionID === "string")
142
142
  value.task_id = value.sessionID;
143
+ delete value.agent;
144
+ delete value.sessionID;
143
145
  }
144
146
  if (name === "read" && typeof value.path === "string" && value.filePath === undefined)
145
147
  value.filePath = value.path;
@@ -177,6 +179,7 @@ function assistantText(message) {
177
179
  const text = [...array(message.content)].reverse().find(part => record(part) && part.type === "text" && typeof part.text === "string" && part.text.trim().length > 0);
178
180
  return record(text) && typeof text.text === "string" ? text.text.trim() : undefined;
179
181
  }
182
+ const V2_SUBAGENT_PROMPT_PREFIX = "You are a subagent spawned by another session.\n";
180
183
  function reportPanel(rendered) {
181
184
  const summary = rendered.indexOf(RETURN_REPORT_MARKER);
182
185
  if (summary < 0)
@@ -257,12 +260,16 @@ async function registerV2Hooks(context, hooks) {
257
260
  if (!record(info) || !record(event.prompt))
258
261
  return;
259
262
  const model = modelReference(info.model) ?? { providerID: "unknown", modelID: "unknown" };
263
+ const nativeText = String(event.prompt.text ?? "");
264
+ const legacyText = typeof info.parentID === "string" && nativeText.startsWith(V2_SUBAGENT_PROMPT_PREFIX)
265
+ ? nativeText.slice(V2_SUBAGENT_PROMPT_PREFIX.length)
266
+ : nativeText;
260
267
  const output = { message: { id: String(event.messageID ?? ""), agent: string(info.agent), model },
261
- parts: [{ type: "text", text: String(event.prompt.text ?? "") }] };
268
+ parts: [{ type: "text", text: legacyText }] };
262
269
  await hooks["chat.message"]({ sessionID: String(event.sessionID), messageID: String(event.messageID ?? ""),
263
270
  ...(typeof info.agent === "string" ? { agent: info.agent } : {}), model }, output);
264
271
  const text = output.parts.find(part => record(part) && part.type === "text" && typeof part.text === "string");
265
- if (record(text) && typeof text.text === "string")
272
+ if (record(text) && typeof text.text === "string" && text.text !== legacyText)
266
273
  event.prompt.text = text.text;
267
274
  if (typeof output.message.agent === "string" && output.message.agent !== info.agent) {
268
275
  await context.session.switchAgent({ sessionID: event.sessionID, agent: output.message.agent });
@@ -145,13 +145,19 @@ For a nontrivial request whose source facts, unit boundaries, or exact validatio
145
145
  do not author a giant speculative plan at the root. Freeze the original request once with stable ordered requirement IDs,
146
146
  including every negative and quality condition, authoritative references, finite proposal read/submission budgets, and the
147
147
  maximum read prefixes. Call ${profile.toolPrefix}begin_operator_proposal. Dispatch its exact ${operator} Task unchanged.
148
+ The begin intent_json has exactly this shape (proposal_budget is the only optional field):
149
+ {"schema_version":"0.1","original_request":{"text":"complete original user request verbatim","source_ref":"user:message-id"},"requirements":[{"id":"R1","text":"exact ordered requirement","kind":"requirement"}],"authoritative_refs":["user:message-id"],"allow_read":["existing/path"]}
150
+ Every requirement entry must include kind="requirement" | "negative" | "quality". Copy original_request.text byte-for-byte.
151
+ Use only authoritative_refs and allow_read for begin intent scope. project_root, source_refs, max_read_prefixes,
152
+ authoritative_references, and other aliases are invalid. Do not guess a smaller contract after rejection: use this exact
153
+ shape, preserving all ordered requirements, and omit proposal_budget only when the deterministic host default is intended.
148
154
  Freeze product requirements from the original request, not extra implementation criteria invented from workflow
149
155
  bookkeeping. Keep proposal read/submission allowances in proposal_budget and host counters; do not turn spent
150
156
  budgets or your own reporting obligations into worker validation commands. Preserve any explicit user requirement.
151
157
  That child remains the same logical operations role: it may inspect only the host-approved read prefixes and submit one
152
158
  requirement-mapped proposal; it cannot edit, use shell, dispatch a worker/advisor/scout, widen read scope, or execute work.
153
159
  An admitted proposal child is never redispatched or replaced. If it terminates without a submitted proposal, report that
154
- 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
155
161
  release the grant before freezing the requirements again. Spent proposal reads and submissions are never restored.
156
162
  Do not cancel/reinvestigate the same known contract defect merely to get another draft. Preserve exact observed
157
163
  commands and correction evidence. If a user-only acceptance condition cannot be expressed by the existing contract,
@@ -336,6 +342,9 @@ When the prompt starts SORTIE_OPERATOR_PROPOSAL or the host supplies SORTIE_PROP
336
342
  perform only its bounded read investigation and submit the complete packet
337
343
  through ${profile.toolPrefix}submit_operator_proposal before returning; a prose-only return is forbidden. If it returns invalid-proposal,
338
344
  repair only the named code (using actual_reads for a budget estimate mismatch) within the finite submission budget.
345
+ When OpenCode V2 exposes that profile tool through Code Mode, use the execute conduit only to call
346
+ ${profile.toolPrefix}submit_operator_proposal. Do not use execute for HTTP, another tool, filesystem access, or computation
347
+ that bypasses this bounded proposal contract. Source inspection remains on the native Read tool.
339
348
  A successful status=submitted ends this investigation Task: return to the parent without further tools, even if a generic
340
349
  continuation asks for the next step. Submission is not execution admission; never call operator_next or dispatch a worker.
341
350
  For an admitted execution queue, call
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sortie-dogs",
3
- "version": "0.10.8",
3
+ "version": "0.10.10",
4
4
  "description": "Bounded agent harness and validated orchestration loop plugin for OpenCode",
5
5
  "keywords": [
6
6
  "opencode",