sortie-dogs 0.12.3 → 0.12.4
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.
- package/README.md +6 -7
- package/dist/core/initialize.js +58 -4
- package/dist/core/operator-mission.d.ts +2 -0
- package/dist/core/operator-mission.js +9 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/plugin/opencode.d.ts +7 -7
- package/dist/plugin/opencode.js +7 -7
- package/dist/plugin/profiled.js +43 -5
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -42,8 +42,8 @@ npm install --save-dev sortie-dogs
|
|
|
42
42
|
npx sortie-dogs init .
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
The package retains the `v010` profile/namespace for compatibility.
|
|
46
|
-
two-level subagent depth to `.opencode/opencode.json`, preserving existing
|
|
45
|
+
The package retains the `v010` profile/namespace for compatibility. `init` adds the OpenCode V2 plugin and the required
|
|
46
|
+
two-level subagent depth to `.opencode/opencode.json(c)`, preserving existing settings:
|
|
47
47
|
|
|
48
48
|
```json
|
|
49
49
|
{
|
|
@@ -62,8 +62,8 @@ Selecting `dog-operator` directly starts the same workflow. `dog-operator` is th
|
|
|
62
62
|
only user-facing v0.10 authority. `dogs-coordinator` and every `*-v010` role are
|
|
63
63
|
internal children and must not be selected as task entry points.
|
|
64
64
|
|
|
65
|
-
`init` installs runtime assets; the `plugins` entry loads runtime enforcement and
|
|
66
|
-
model routing.
|
|
65
|
+
`init` installs runtime assets and merges the required OpenCode settings; the `plugins` entry loads runtime enforcement and
|
|
66
|
+
model routing. A new session alone does not reload an updated
|
|
67
67
|
plugin process, so restart OpenCode after installation or upgrade.
|
|
68
68
|
|
|
69
69
|
## v0.12.0 workflow
|
|
@@ -262,9 +262,8 @@ npm install --global sortie-dogs
|
|
|
262
262
|
sortie-dogs init --global --profile v010
|
|
263
263
|
```
|
|
264
264
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
default agent or merge user settings.
|
|
265
|
+
Global initialization also merges `sortie-dogs` and `experimental.subagent_depth: 2` into the
|
|
266
|
+
global OpenCode config, preserving unrelated settings and the default agent.
|
|
268
267
|
|
|
269
268
|
## Updates and removal
|
|
270
269
|
|
package/dist/core/initialize.js
CHANGED
|
@@ -11,6 +11,7 @@ import { constants } from "node:fs";
|
|
|
11
11
|
import { lstat, mkdir, open, readFile, realpath, rm, rmdir, stat } from "node:fs/promises";
|
|
12
12
|
import { homedir } from "node:os";
|
|
13
13
|
import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
14
|
+
import { applyEdits, modify, parse } from "jsonc-parser";
|
|
14
15
|
import { RUNTIME_PROFILES } from "./runtime-profile.js";
|
|
15
16
|
const assets = await import(__rewriteRelativeImportExtension(`../runtime-assets.${import.meta.url.endsWith(".ts") ? "ts" : "js"}`));
|
|
16
17
|
const { runtimeAssets } = assets;
|
|
@@ -348,6 +349,46 @@ function layoutLegacyPath(asset, layout) {
|
|
|
348
349
|
? asset.relativePath.slice(`${OPEN_CODE_DIRECTORY}/`.length)
|
|
349
350
|
: asset.relativePath;
|
|
350
351
|
}
|
|
352
|
+
async function v010Config(root, global) {
|
|
353
|
+
const prefix = global ? "" : `${OPEN_CODE_DIRECTORY}/`;
|
|
354
|
+
const json = `${prefix}opencode.json`, jsonc = `${prefix}opencode.jsonc`;
|
|
355
|
+
const presentJSON = await assertSafeExistingPath(root, json, true);
|
|
356
|
+
const presentJSONC = await assertSafeExistingPath(root, jsonc, true);
|
|
357
|
+
if (presentJSON && presentJSONC)
|
|
358
|
+
throw new ProjectInitializationError("conflict", "Both OpenCode JSON and JSONC configs exist; choose one before initialization.");
|
|
359
|
+
const relativePath = presentJSONC ? jsonc : json;
|
|
360
|
+
const previous = presentJSONC || presentJSON ? await readFile(resolve(root, relativePath)) : undefined;
|
|
361
|
+
let content = previous?.toString("utf8") ?? "{}\n";
|
|
362
|
+
const errors = [];
|
|
363
|
+
const config = parse(content, errors, { allowTrailingComma: true });
|
|
364
|
+
if (errors.length || config === null || typeof config !== "object" || Array.isArray(config)) {
|
|
365
|
+
throw new ProjectInitializationError("conflict", `OpenCode config ${relativePath} must be a valid JSON/JSONC object.`);
|
|
366
|
+
}
|
|
367
|
+
const current = config;
|
|
368
|
+
const plugins = current.plugins;
|
|
369
|
+
if (plugins !== undefined && (!Array.isArray(plugins) || !plugins.every(item => typeof item === "string" ||
|
|
370
|
+
(item !== null && typeof item === "object" && !Array.isArray(item) && typeof item.package === "string")))) {
|
|
371
|
+
throw new ProjectInitializationError("conflict", `OpenCode config ${relativePath} has an invalid plugins list.`);
|
|
372
|
+
}
|
|
373
|
+
if (current.experimental !== undefined && (current.experimental === null || typeof current.experimental !== "object" ||
|
|
374
|
+
Array.isArray(current.experimental)))
|
|
375
|
+
throw new ProjectInitializationError("conflict", `OpenCode config ${relativePath} has invalid experimental settings.`);
|
|
376
|
+
const depth = current.experimental?.subagent_depth;
|
|
377
|
+
if (depth !== undefined && (!Number.isSafeInteger(depth) || depth < 0)) {
|
|
378
|
+
throw new ProjectInitializationError("conflict", `OpenCode config ${relativePath} has an invalid subagent depth.`);
|
|
379
|
+
}
|
|
380
|
+
const options = { formattingOptions: { insertSpaces: true, tabSize: 2 } };
|
|
381
|
+
const update = (path, value) => { content = applyEdits(content, modify(content, path, value, options)); };
|
|
382
|
+
if (!plugins?.some(item => item === "sortie-dogs" ||
|
|
383
|
+
(item !== null && typeof item === "object" && item.package === "sortie-dogs"))) {
|
|
384
|
+
update(["plugins"], [...(plugins ?? []), "sortie-dogs"]);
|
|
385
|
+
}
|
|
386
|
+
if (depth === undefined || depth < 2)
|
|
387
|
+
update(["experimental", "subagent_depth"], 2);
|
|
388
|
+
if (previous?.toString("utf8") === content)
|
|
389
|
+
return undefined;
|
|
390
|
+
return { entry: { relativePath, content }, previous };
|
|
391
|
+
}
|
|
351
392
|
async function initializeRoot(requestedRoot, layout, installAssets = runtimeAssets) {
|
|
352
393
|
const root = resolve(requestedRoot);
|
|
353
394
|
const legacyAssets = layout.legacyAssets ?? LEGACY_RUNTIME_ASSETS;
|
|
@@ -355,6 +396,7 @@ async function initializeRoot(requestedRoot, layout, installAssets = runtimeAsse
|
|
|
355
396
|
if (rootInfo === undefined || !rootInfo.isDirectory() || rootInfo.isSymbolicLink()) {
|
|
356
397
|
throw new ProjectInitializationError("invalid-project", layout.invalidRootMessage);
|
|
357
398
|
}
|
|
399
|
+
const configUpdate = layout.configureV010 ? await v010Config(root, layout.assetPrefix === "") : undefined;
|
|
358
400
|
const version = assetVersion(installAssets);
|
|
359
401
|
const assetEntries = installAssets.map(({ installPath, content }) => ({
|
|
360
402
|
relativePath: safeAssetPath(installPath, layout.assetPrefix),
|
|
@@ -381,7 +423,7 @@ async function initializeRoot(requestedRoot, layout, installAssets = runtimeAsse
|
|
|
381
423
|
const markerText = existing[markerIndex];
|
|
382
424
|
const matches = (entry, index) => existing[index]?.equals(Buffer.from(entry.content)) ?? false;
|
|
383
425
|
const assetsMatch = assetEntries.every(matches);
|
|
384
|
-
if (markerText !== undefined && parseMarker(markerText.toString("utf8")) === version && assetsMatch) {
|
|
426
|
+
if (markerText !== undefined && parseMarker(markerText.toString("utf8")) === version && assetsMatch && !configUpdate) {
|
|
385
427
|
if (layout.controlIgnore ?? !layout.preserveAllLegacy)
|
|
386
428
|
await ensureProjectLunaControlIgnore(root);
|
|
387
429
|
return {
|
|
@@ -449,6 +491,18 @@ async function initializeRoot(requestedRoot, layout, installAssets = runtimeAsse
|
|
|
449
491
|
});
|
|
450
492
|
}
|
|
451
493
|
}
|
|
494
|
+
if (configUpdate) {
|
|
495
|
+
const { entry, previous } = configUpdate;
|
|
496
|
+
const parent = dirname(entry.relativePath).replaceAll("\\", "/");
|
|
497
|
+
if (parent !== ".")
|
|
498
|
+
await ensureDirectory(root, parent, createdDirectories);
|
|
499
|
+
if (previous === undefined)
|
|
500
|
+
await createFile(resolve(root, entry.relativePath), entry.content, createdFiles);
|
|
501
|
+
else
|
|
502
|
+
await overwriteFileSafely(root, entry.relativePath, entry.content, () => {
|
|
503
|
+
modifiedFiles.push({ relativePath: entry.relativePath, content: previous });
|
|
504
|
+
});
|
|
505
|
+
}
|
|
452
506
|
for (const { asset } of removableLegacyFiles) {
|
|
453
507
|
const state = await readableOwnedLegacyFile(root, asset);
|
|
454
508
|
if (!Buffer.isBuffer(state)) {
|
|
@@ -478,7 +532,7 @@ async function initializeRoot(requestedRoot, layout, installAssets = runtimeAsse
|
|
|
478
532
|
return {
|
|
479
533
|
status: "installed",
|
|
480
534
|
version,
|
|
481
|
-
installedPaths: entries.map(({ relativePath }) => relativePath),
|
|
535
|
+
installedPaths: [...entries.map(({ relativePath }) => relativePath), ...(configUpdate ? [configUpdate.entry.relativePath] : [])],
|
|
482
536
|
preservedLegacyPaths,
|
|
483
537
|
};
|
|
484
538
|
}
|
|
@@ -502,7 +556,7 @@ export async function resolveGlobalConfigRoot(env = process.env, home = homedir(
|
|
|
502
556
|
return resolve(env.XDG_CONFIG_HOME, "opencode");
|
|
503
557
|
return resolve(home, ".config", "opencode");
|
|
504
558
|
}
|
|
505
|
-
/** Installs the packaged runtime into one existing project
|
|
559
|
+
/** Installs the packaged runtime into one existing project, preserving unrelated user settings. */
|
|
506
560
|
async function profileInstallation(id, global) {
|
|
507
561
|
if (!Object.hasOwn(RUNTIME_PROFILES, id))
|
|
508
562
|
throw new ProjectInitializationError("invalid-project", "Unknown runtime profile.");
|
|
@@ -515,7 +569,7 @@ async function profileInstallation(id, global) {
|
|
|
515
569
|
return { layout: { ...(global ? GLOBAL_LAYOUT : PROJECT_LAYOUT),
|
|
516
570
|
markerPath: global ? profile.markerFile : `${OPEN_CODE_DIRECTORY}/${profile.markerFile}`,
|
|
517
571
|
preserveAllLegacy: false, controlIgnore: false, legacyAssets: V010_ROLE_NAME_LEGACY_ASSETS,
|
|
518
|
-
renamedTargets: ["agent/dog-operator.md", "agent/dogs-coordinator.md"] }, assets: module.runtimeAssets };
|
|
572
|
+
renamedTargets: ["agent/dog-operator.md", "agent/dogs-coordinator.md"], configureV010: true }, assets: module.runtimeAssets };
|
|
519
573
|
}
|
|
520
574
|
export async function initializeProject(projectRoot = process.cwd(), profile = "stable") {
|
|
521
575
|
const install = await profileInstallation(profile, false);
|
|
@@ -60,6 +60,8 @@ export declare class OperatorMissionRuntime {
|
|
|
60
60
|
update(root: string, change: (state: OperatorMission) => void): Promise<OperatorMission>;
|
|
61
61
|
task(state: OperatorMission): OperatorTask;
|
|
62
62
|
admit(root: string, callID: string, args: Record<string, unknown>): Promise<OperatorMission>;
|
|
63
|
+
/** Reopen only the exact dispatch whose native Task has a terminal record after a server restart. */
|
|
64
|
+
reconcileFinishedDispatch(root: string, missionID: string, callID: string): Promise<OperatorMission>;
|
|
63
65
|
claim(root: string, child: string, prompt: string): Promise<string>;
|
|
64
66
|
brief(state: OperatorMission): string;
|
|
65
67
|
}
|
|
@@ -144,6 +144,15 @@ export class OperatorMissionRuntime {
|
|
|
144
144
|
state.submission = null;
|
|
145
145
|
});
|
|
146
146
|
}
|
|
147
|
+
/** Reopen only the exact dispatch whose native Task has a terminal record after a server restart. */
|
|
148
|
+
reconcileFinishedDispatch(root, missionID, callID) {
|
|
149
|
+
return this.update(root, state => {
|
|
150
|
+
if (state.id !== missionID || state.callID !== callID || !state.dispatchOpen ||
|
|
151
|
+
["cancelled", "completed"].includes(state.phase))
|
|
152
|
+
throw new Error("mission-dispatch-reconciliation-stale");
|
|
153
|
+
state.dispatchOpen = false;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
147
156
|
async claim(root, child, prompt) {
|
|
148
157
|
const state = await this.update(root, state => {
|
|
149
158
|
if (state.phase !== "running" || !state.dispatchOpen || !state.callID ||
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { default } from "./plugin/v2.js";
|
|
1
2
|
export { lintHandoff } from "./core/validate-semantics.js";
|
|
2
3
|
export { validateWorktreeParallelContract } from "./core/validate-worktree-parallel.js";
|
|
3
4
|
export { LUNA_FABRIC_CONTRACT_VERSION, admitLunaFabric, } from "./core/luna-fabric-contract.js";
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// OpenCode V2 loads the package root named in `plugins`; keep the public named API intact.
|
|
2
|
+
export { default } from "./plugin/v2.js";
|
|
1
3
|
export { lintHandoff } from "./core/validate-semantics.js";
|
|
2
4
|
export { validateWorktreeParallelContract } from "./core/validate-worktree-parallel.js";
|
|
3
5
|
export { LUNA_FABRIC_CONTRACT_VERSION, admitLunaFabric, } from "./core/luna-fabric-contract.js";
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* OpenCode V2's package loader resolves the `./plugin` export. Expose its
|
|
3
|
+
* default definition here as well as the named V1 factory used by wrappers.
|
|
3
4
|
*
|
|
4
|
-
* OpenCode configuration lists the package name in the `
|
|
5
|
-
* `"
|
|
5
|
+
* OpenCode configuration lists the package name in the `plugins` array:
|
|
6
|
+
* `"plugins": ["sortie-dogs"]`. V1 wrappers can still use
|
|
6
7
|
* `import { SortieDogsPlugin } from "sortie-dogs/plugin"`.
|
|
7
8
|
*
|
|
8
|
-
* OpenCode
|
|
9
|
-
*
|
|
10
|
-
* load and silently disables the plugin. This entry exposes the factory and nothing else; every
|
|
11
|
-
* other runtime symbol stays on the package root.
|
|
9
|
+
* OpenCode V2 requires a default definition with an id and setup or effect function.
|
|
10
|
+
* Keep other runtime symbols on the package root rather than this shared entry.
|
|
12
11
|
*/
|
|
13
12
|
export { SortieDogsV010Plugin as SortieDogsPlugin } from "./profiled.js";
|
|
13
|
+
export { default } from "./v2.js";
|
|
14
14
|
export type { OpenCodeEvent, OpenCodeHooks, OpenCodePlugin, OpenCodePluginInput, SortieDogsPluginOptions, } from "./index.js";
|
package/dist/plugin/opencode.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* OpenCode V2's package loader resolves the `./plugin` export. Expose its
|
|
3
|
+
* default definition here as well as the named V1 factory used by wrappers.
|
|
3
4
|
*
|
|
4
|
-
* OpenCode configuration lists the package name in the `
|
|
5
|
-
* `"
|
|
5
|
+
* OpenCode configuration lists the package name in the `plugins` array:
|
|
6
|
+
* `"plugins": ["sortie-dogs"]`. V1 wrappers can still use
|
|
6
7
|
* `import { SortieDogsPlugin } from "sortie-dogs/plugin"`.
|
|
7
8
|
*
|
|
8
|
-
* OpenCode
|
|
9
|
-
*
|
|
10
|
-
* load and silently disables the plugin. This entry exposes the factory and nothing else; every
|
|
11
|
-
* other runtime symbol stays on the package root.
|
|
9
|
+
* OpenCode V2 requires a default definition with an id and setup or effect function.
|
|
10
|
+
* Keep other runtime symbols on the package root rather than this shared entry.
|
|
12
11
|
*/
|
|
13
12
|
export { SortieDogsV010Plugin as SortieDogsPlugin } from "./profiled.js";
|
|
13
|
+
export { default } from "./v2.js";
|
package/dist/plugin/profiled.js
CHANGED
|
@@ -376,6 +376,35 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
376
376
|
throw new Error("mission-controller-required");
|
|
377
377
|
return { root, mission };
|
|
378
378
|
}
|
|
379
|
+
async function reconcileMissionDispatch(root) {
|
|
380
|
+
const mission = await missions.read(root);
|
|
381
|
+
if (!mission?.dispatchOpen || !mission.callID || ["cancelled", "completed"].includes(mission.phase) ||
|
|
382
|
+
taskOwners.has(mission.callID))
|
|
383
|
+
return mission;
|
|
384
|
+
const history = await messages(root);
|
|
385
|
+
const finished = history.flatMap(message => Array.isArray(message.parts) ? message.parts : []).filter(part => record(part) && part.type === "tool" && part.tool === "task" && part.callID === mission.callID &&
|
|
386
|
+
record(part.state) && ["completed", "error"].includes(String(part.state.status)) &&
|
|
387
|
+
record(part.state.input) && part.state.input.subagent_type === profileAgent(profile, "dog-operator") &&
|
|
388
|
+
(part.state.input.task_id === undefined || part.state.input.task_id === mission.coordinator) &&
|
|
389
|
+
(mission.coordinator !== null || (part.state.input.prompt === missions.task(mission).prompt &&
|
|
390
|
+
part.state.input.task_id === undefined)));
|
|
391
|
+
if (finished.length !== 1)
|
|
392
|
+
return mission;
|
|
393
|
+
if (mission.coordinator !== null) {
|
|
394
|
+
const who = await identity(mission.coordinator);
|
|
395
|
+
if (who.parent !== root || who.role !== "dog-operator")
|
|
396
|
+
return mission;
|
|
397
|
+
}
|
|
398
|
+
return missions.reconcileFinishedDispatch(root, mission.id, mission.callID);
|
|
399
|
+
}
|
|
400
|
+
function missionDispatchPacket(mission, run) {
|
|
401
|
+
const packet = missionPacket(mission, run);
|
|
402
|
+
if (!mission.dispatchOpen && ["open", "running"].includes(mission.phase)) {
|
|
403
|
+
return { ...packet, task: missions.task(mission),
|
|
404
|
+
next_action: "The previous Coordinator Task is finished. Dispatch this exact Task to continue the same mission and Coordinator session; keep the original requirements and cumulative budget." };
|
|
405
|
+
}
|
|
406
|
+
return packet;
|
|
407
|
+
}
|
|
379
408
|
async function restorePriorAcceptance(root, state) {
|
|
380
409
|
const succeeded = [...state.units].reverse().find(unit => unit.status === "succeeded");
|
|
381
410
|
const current = succeeded === undefined ? undefined : {
|
|
@@ -608,12 +637,17 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
608
637
|
const root = await rootFor(context.sessionID);
|
|
609
638
|
if (!root)
|
|
610
639
|
throw new Error("operator-grant-invalid");
|
|
640
|
+
const activeMission = await reconcileMissionDispatch(root);
|
|
641
|
+
if (activeMission && context.sessionID === root && !activeMission.dispatchOpen &&
|
|
642
|
+
["open", "running"].includes(activeMission.phase)) {
|
|
643
|
+
return JSON.stringify({ ...missionDispatchPacket(activeMission, await operators.read(root)), budget: await control.currentBudget(root) });
|
|
644
|
+
}
|
|
611
645
|
const result = await operators.next(root, context.sessionID);
|
|
612
646
|
const mission = await missions.read(root);
|
|
613
647
|
if (mission && !["cancelled", "completed"].includes(mission.phase)) {
|
|
614
648
|
if (record(result) && record(result.task))
|
|
615
649
|
return JSON.stringify(result);
|
|
616
|
-
return JSON.stringify({ ...
|
|
650
|
+
return JSON.stringify({ ...missionDispatchPacket(mission, await operators.required(root)), budget: await control.currentBudget(root) });
|
|
617
651
|
}
|
|
618
652
|
if (context.sessionID === root && record(result) && record(result.task)) {
|
|
619
653
|
const state = await operators.required(root);
|
|
@@ -652,9 +686,9 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
652
686
|
tools[status] = { description: "Read the durable root-owned operator outcome and host budget counters (max_units, consumed_units, reserved_units, remaining_units) without claiming acceptance or retrying work. An investigating proposal returns its exact short Task reference only before a Task has been admitted; an existing admission never yields a redispatch Task.",
|
|
653
687
|
args: {}, execute: async (_args, context) => {
|
|
654
688
|
const root = await rootFor(context.sessionID);
|
|
655
|
-
const mission = root ? await missions.read(root) : undefined;
|
|
689
|
+
const mission = root && context.sessionID === root ? await reconcileMissionDispatch(root) : root ? await missions.read(root) : undefined;
|
|
656
690
|
if (mission && (context.sessionID === root || context.sessionID === mission.coordinator)) {
|
|
657
|
-
return JSON.stringify({ ...
|
|
691
|
+
return JSON.stringify({ ...missionDispatchPacket(mission, await operators.read(root)), budget: await control.currentBudget(root) });
|
|
658
692
|
}
|
|
659
693
|
await requireRoot(context.sessionID);
|
|
660
694
|
const state = await operators.read(context.sessionID);
|
|
@@ -1083,9 +1117,12 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1083
1117
|
tools[startMission] = { description: "Operator: save a few one-line requirements/negative constraints. The original user message, IDs and ownership are captured automatically. Dispatch the returned Coordinator task immediately, or use plan_units for a simple single-unit Fast-lane task.",
|
|
1084
1118
|
args: { requirements: { ...stringList, minItems: 1, maxItems: 64 } }, execute: async (args, context) => {
|
|
1085
1119
|
await requireRoot(context.sessionID);
|
|
1120
|
+
await reconcileMissionDispatch(context.sessionID);
|
|
1086
1121
|
const mission = await missions.start(context.sessionID, args.requirements);
|
|
1087
|
-
return JSON.stringify(
|
|
1088
|
-
|
|
1122
|
+
return JSON.stringify(mission.dispatchOpen
|
|
1123
|
+
? missionDispatchPacket(mission, await operators.read(context.sessionID))
|
|
1124
|
+
: { mission_id: mission.id, requirements: mission.requirements, task: missions.task(mission),
|
|
1125
|
+
next_action: "Nontrivial: dispatch task now. Simple single-unit work with known scope/check: call plan_units directly. Do not create a proposal or ask for plan approval." });
|
|
1089
1126
|
} };
|
|
1090
1127
|
async function declareMissionUnits(root, actor, mission, raw, reason) {
|
|
1091
1128
|
const plan = missionPlan(mission, raw);
|
|
@@ -1498,6 +1535,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1498
1535
|
if (request.tool === "task" && args.subagent_type === profileAgent(profile, "dog-operator") &&
|
|
1499
1536
|
mission && !["completed", "cancelled"].includes(mission.phase)) {
|
|
1500
1537
|
await requireRoot(request.sessionID);
|
|
1538
|
+
await reconcileMissionDispatch(root);
|
|
1501
1539
|
await missions.admit(root, request.callID, args);
|
|
1502
1540
|
taskOwners.set(request.callID, { root, actor: request.sessionID, operator: false, mission: true });
|
|
1503
1541
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sortie-dogs",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.4",
|
|
4
4
|
"description": "Bounded agent harness and validated orchestration loop plugin for OpenCode",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"opencode",
|
|
@@ -72,7 +72,8 @@
|
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
74
|
"ajv": "^8.17.1",
|
|
75
|
-
"ajv-formats": "^3.0.1"
|
|
75
|
+
"ajv-formats": "^3.0.1",
|
|
76
|
+
"jsonc-parser": "^3.3.1"
|
|
76
77
|
},
|
|
77
78
|
"devDependencies": {
|
|
78
79
|
"@types/node": "^22.0.0",
|