@mstar-harness/dsh 2.2.0 → 2.3.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.
- package/README.i18n.yaml +2 -3
- package/README.md +118 -3
- package/README.zh.md +118 -3
- package/bundle/README.md +11 -1
- package/dist/client/panel/graph/event-log.d.ts +18 -3
- package/dist/client/panel/graph/project-graph.d.ts +40 -14
- package/dist/client/panel/locale.d.ts +1 -1
- package/dist/client.js +131 -17
- package/dist/gates/_shared.d.ts +104 -5
- package/dist/gates/adapter.d.ts +34 -1
- package/dist/gates/agent-flow.d.ts +186 -4
- package/dist/gates/agent-personas.d.ts +44 -0
- package/dist/gates/dispatch.d.ts +64 -2
- package/dist/gates/fallbacks-advisory.d.ts +77 -0
- package/dist/gates/fallbacks-decoration.d.ts +108 -0
- package/dist/gates/fallbacks-probe.d.ts +63 -0
- package/dist/gates/fallbacks-seeds.d.ts +98 -0
- package/dist/gates/goal-bridge.d.ts +151 -0
- package/dist/gates/plan-mode-bridge.d.ts +70 -0
- package/dist/gates/skill-lint.d.ts +4 -3
- package/dist/gates/system-prompt.d.ts +121 -0
- package/dist/gates/workflow-ledger.d.ts +149 -0
- package/dist/gates/workflow-policy.d.ts +203 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.js +1833 -60
- package/dist/types.d.ts +30 -5
- package/harness-agents/architect.md +35 -0
- package/harness-agents/code-reviewer.md +98 -0
- package/harness-agents/frontend-dev.md +33 -0
- package/harness-agents/fullstack-dev-2.md +33 -0
- package/harness-agents/fullstack-dev.md +33 -0
- package/harness-agents/ops-engineer.md +33 -0
- package/harness-agents/product-manager.md +35 -0
- package/harness-agents/project-manager.md +32 -0
- package/harness-agents/prompt-engineer.md +33 -0
- package/harness-agents/qa-engineer.md +35 -0
- package/harness-agents/qc-specialist-2.md +62 -0
- package/harness-agents/qc-specialist-3.md +62 -0
- package/harness-agents/qc-specialist.md +62 -0
- package/harness-agents/writing-specialist.md +35 -0
- package/harness-commands/codebase-audit.md +2 -2
- package/harness-skills/mstar-audit/SKILL.md +20 -0
- package/harness-skills/mstar-audit/references/audit-playbook.md +27 -0
- package/harness-skills/mstar-branch-worktree/SKILL.md +7 -0
- package/harness-skills/mstar-coding-behavior/SKILL.md +5 -0
- package/harness-skills/mstar-compound/references/compound-workflow.md +16 -0
- package/harness-skills/mstar-compound-refresh/SKILL.md +5 -0
- package/harness-skills/mstar-iteration/references/phase-4-5-pr-delivery.md +1 -0
- package/harness-skills/mstar-plan-artifacts/SKILL.md +1 -0
- package/harness-skills/mstar-plan-artifacts/references/status-and-residuals.md +8 -0
- package/harness-skills/mstar-review-qc/SKILL.md +6 -0
- package/harness-skills/mstar-roles/references/project-manager/qc-and-residuals.md +2 -1
- package/harness-skills/mstar-roles/references/qc-specialist/deep-review-lenses.md +16 -7
- package/harness-skills/mstar-roles/references/qc-specialist/report-template.md +17 -1
- package/harness-skills/mstar-roles/references/qc-specialist-shared.md +3 -1
- package/harness-skills/mstar-roles/references/writing-specialist.md +33 -0
- package/harness-skills/mstar-sdd/SKILL.md +5 -1
- package/harness-skills/mstar-sdd/references/file-handoffs.md +2 -0
- package/harness-skills/mstar-skill-authoring/SKILL.md +4 -2
- package/harness-skills/mstar-skill-authoring/references/regression-fixation.md +46 -0
- package/harness-skills/mstar-skill-authoring/references/skillsbench-authoring.md +37 -0
- package/package.json +7 -4
package/dist/index.js
CHANGED
|
@@ -1106,8 +1106,8 @@ var require_lib2 = __commonJS((exports, module) => {
|
|
|
1106
1106
|
});
|
|
1107
1107
|
|
|
1108
1108
|
// src/index.ts
|
|
1109
|
-
import { existsSync as
|
|
1110
|
-
import { join as
|
|
1109
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15, readdirSync as readdirSync9 } from "node:fs";
|
|
1110
|
+
import { join as join20 } from "node:path";
|
|
1111
1111
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
1112
1112
|
import {
|
|
1113
1113
|
apply as applySkillLocal,
|
|
@@ -1473,7 +1473,7 @@ function parseAssignmentBranchForms(assignmentText) {
|
|
|
1473
1473
|
if (fields.branchPolicy !== undefined && fields.branchPolicy !== "") {
|
|
1474
1474
|
const direct = fields.branchPolicy.match(/^direct\s+on\s+(\S+)/i);
|
|
1475
1475
|
if (direct) {
|
|
1476
|
-
const strict = fields.branchPolicy.match(/^direct\s+on\s+(\S+)(?:\s*(?:[
|
|
1476
|
+
const strict = fields.branchPolicy.match(/^direct\s+on\s+(\S+)(?:\s*(?:[\u2014\u2013]|--|-)\s*(.+))?$/);
|
|
1477
1477
|
forms.directOn = { branch: direct[1].trim(), reason: strict ? (strict[2] ?? "").trim() : "" };
|
|
1478
1478
|
}
|
|
1479
1479
|
}
|
|
@@ -3337,6 +3337,26 @@ function scopeGuard(path, allowedRoots) {
|
|
|
3337
3337
|
function violation9(severity, code, message, fix) {
|
|
3338
3338
|
return { ok: false, severity, code, message, fix };
|
|
3339
3339
|
}
|
|
3340
|
+
var TASK_ARTIFACT_RE = /\btask-\d+(?:-(?:brief|report|fix-report|diff)|\.diff)\b/g;
|
|
3341
|
+
var SDD_DEEPLINK_RE = /\.(?:mstar|agents)\/sdd\/([^\s/<>{}\[\]"'\*\?]+)/g;
|
|
3342
|
+
function findEphemeralCitations(skillText) {
|
|
3343
|
+
const citations = [];
|
|
3344
|
+
const lines = skillText.split(/\r?\n/);
|
|
3345
|
+
for (let i = 0;i < lines.length; i++) {
|
|
3346
|
+
const found = [];
|
|
3347
|
+
for (const m of lines[i].matchAll(TASK_ARTIFACT_RE)) {
|
|
3348
|
+
found.push({ index: m.index, match: m[0], kind: "task-artifact" });
|
|
3349
|
+
}
|
|
3350
|
+
for (const m of lines[i].matchAll(SDD_DEEPLINK_RE)) {
|
|
3351
|
+
found.push({ index: m.index, match: m[0], kind: "sdd-deeplink" });
|
|
3352
|
+
}
|
|
3353
|
+
found.sort((a, b) => a.index - b.index);
|
|
3354
|
+
for (const f of found) {
|
|
3355
|
+
citations.push({ line: i + 1, match: f.match, kind: f.kind });
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
return citations;
|
|
3359
|
+
}
|
|
3340
3360
|
var WORKFLOW_VERB_START_RE = /^(?:explains?|describes?|covers?|provides?|walks?|guides?|shows?|lists?|details?|demonstrates?|outlines?|teaches?|summarizes?)\b/i;
|
|
3341
3361
|
var PRONOUN_RE = /\bI\b(?!\/)|\b(?:we|you|my|our|your|us)\b/gi;
|
|
3342
3362
|
var DESCRIPTION_MAX_WORDS = 120;
|
|
@@ -3640,6 +3660,19 @@ import { existsSync as existsSync7 } from "node:fs";
|
|
|
3640
3660
|
import { resolve as resolve7 } from "node:path";
|
|
3641
3661
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3642
3662
|
var STATUS_FILE = "status.json";
|
|
3663
|
+
var PERSONA_INTERPOLATION_HAZARD = /\{\{[\s\S]*\}\}/;
|
|
3664
|
+
function stripInterpolationHazard(text) {
|
|
3665
|
+
let out = text;
|
|
3666
|
+
for (;; ) {
|
|
3667
|
+
const open = out.indexOf("{{");
|
|
3668
|
+
if (open < 0)
|
|
3669
|
+
return out;
|
|
3670
|
+
const close = out.indexOf("}}", open + 2);
|
|
3671
|
+
if (close < 0)
|
|
3672
|
+
return out;
|
|
3673
|
+
out = `${out.slice(0, open)}{ {${out.slice(open + 2, close)}} }${out.slice(close + 2)}`;
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3643
3676
|
var Config = import_schemastery.default.object({
|
|
3644
3677
|
harnessDir: import_schemastery.default.string(),
|
|
3645
3678
|
enforcement: import_schemastery.default.union(["hard", "soft"]),
|
|
@@ -3647,7 +3680,19 @@ var Config = import_schemastery.default.object({
|
|
|
3647
3680
|
dispatchBinding: import_schemastery.default.string().default(undefined),
|
|
3648
3681
|
skillRoots: import_schemastery.default.array(import_schemastery.default.string()).default(undefined),
|
|
3649
3682
|
bundledSkillDir: import_schemastery.default.string().default(undefined),
|
|
3650
|
-
catalogTtlMs: import_schemastery.default.number().default(undefined)
|
|
3683
|
+
catalogTtlMs: import_schemastery.default.number().default(undefined),
|
|
3684
|
+
roleMap: import_schemastery.default.dict(import_schemastery.default.string()).default(undefined),
|
|
3685
|
+
rolePersonas: import_schemastery.default.transform(import_schemastery.default.dict(import_schemastery.default.string()), (value) => {
|
|
3686
|
+
for (const [role, persona] of Object.entries(value)) {
|
|
3687
|
+
if (PERSONA_INTERPOLATION_HAZARD.test(persona)) {
|
|
3688
|
+
throw new import_schemastery.default.ValidationError(`rolePersonas["${role}"] must not contain a "{{" paired with a later "}}" (dsh system-prompt strict interpolation renders persona text and throws on unknown or malformed references — use single braces or reword)`, {});
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3691
|
+
return value;
|
|
3692
|
+
}).default(undefined),
|
|
3693
|
+
workflowGate: import_schemastery.default.union(["off", "warn", "ask", "hard"]).default("warn"),
|
|
3694
|
+
workflowNames: import_schemastery.default.array(import_schemastery.default.string()).default(undefined),
|
|
3695
|
+
maxGoalRounds: import_schemastery.default.number().default(undefined)
|
|
3651
3696
|
});
|
|
3652
3697
|
function formatViolation(violation12) {
|
|
3653
3698
|
return `[${violation12.severity}] ${violation12.code}: ${violation12.message}${violation12.fix !== undefined ? ` (fix: ${violation12.fix})` : ""}`;
|
|
@@ -3681,6 +3726,25 @@ function packagedSkillsDir() {
|
|
|
3681
3726
|
packagedSkillsDirMemo = dir;
|
|
3682
3727
|
return dir;
|
|
3683
3728
|
}
|
|
3729
|
+
function resolvePackagedAgentsDir(fileUrl) {
|
|
3730
|
+
for (const rel of ["../harness-agents", "../../harness-agents"]) {
|
|
3731
|
+
try {
|
|
3732
|
+
const dir = fileURLToPath2(new URL(rel, fileUrl));
|
|
3733
|
+
if (existsSync7(dir))
|
|
3734
|
+
return dir;
|
|
3735
|
+
} catch {}
|
|
3736
|
+
}
|
|
3737
|
+
return;
|
|
3738
|
+
}
|
|
3739
|
+
var packagedAgentsDirMemo;
|
|
3740
|
+
function packagedAgentsDir() {
|
|
3741
|
+
if (packagedAgentsDirMemo !== undefined)
|
|
3742
|
+
return packagedAgentsDirMemo;
|
|
3743
|
+
const dir = resolvePackagedAgentsDir(import.meta.url);
|
|
3744
|
+
if (dir !== undefined)
|
|
3745
|
+
packagedAgentsDirMemo = dir;
|
|
3746
|
+
return dir;
|
|
3747
|
+
}
|
|
3684
3748
|
|
|
3685
3749
|
class HarnessResolver {
|
|
3686
3750
|
explicit;
|
|
@@ -3745,8 +3809,99 @@ import { join as join10 } from "node:path";
|
|
|
3745
3809
|
// src/gates/dispatch.ts
|
|
3746
3810
|
import { existsSync as existsSync9 } from "node:fs";
|
|
3747
3811
|
import { basename as basename5, join as join6, resolve as resolve9 } from "node:path";
|
|
3812
|
+
|
|
3813
|
+
// src/gates/workflow-policy.ts
|
|
3814
|
+
var WORKFLOW_NAME_UNKNOWN_CODE = "workflow.name.unknown";
|
|
3815
|
+
var WORKFLOW_LEASE_UNCOVERED_CODE = "workflow.lease.uncovered";
|
|
3816
|
+
var WORKFLOW_NAME_CONTROL_CHARS = /[\u0000-\u001F\u007F]/g;
|
|
3817
|
+
function normalizeWorkflowName(value) {
|
|
3818
|
+
return value.replace(WORKFLOW_NAME_CONTROL_CHARS, "");
|
|
3819
|
+
}
|
|
3820
|
+
function workflowNameUnknown(config, metaName) {
|
|
3821
|
+
const names = config.workflowNames;
|
|
3822
|
+
if (names === undefined || names.length === 0)
|
|
3823
|
+
return true;
|
|
3824
|
+
return !names.some((entry) => normalizeWorkflowName(entry) === metaName);
|
|
3825
|
+
}
|
|
3826
|
+
|
|
3827
|
+
class WorkflowAskCache {
|
|
3828
|
+
decisions = new Map;
|
|
3829
|
+
asked = new Set;
|
|
3830
|
+
get(name) {
|
|
3831
|
+
return this.decisions.get(name);
|
|
3832
|
+
}
|
|
3833
|
+
record(name, decision) {
|
|
3834
|
+
this.decisions.set(normalizeWorkflowName(name), decision);
|
|
3835
|
+
}
|
|
3836
|
+
markAsked(name) {
|
|
3837
|
+
this.asked.add(normalizeWorkflowName(name));
|
|
3838
|
+
}
|
|
3839
|
+
wasAsked(name) {
|
|
3840
|
+
return this.asked.has(normalizeWorkflowName(name));
|
|
3841
|
+
}
|
|
3842
|
+
}
|
|
3843
|
+
function workflowPolicy(config, cache, input) {
|
|
3844
|
+
const mode = config.workflowGate ?? "warn";
|
|
3845
|
+
if (mode === "off")
|
|
3846
|
+
return { decision: "allow" };
|
|
3847
|
+
if (input.uncoveredPlanId !== undefined) {
|
|
3848
|
+
if (mode === "hard") {
|
|
3849
|
+
return {
|
|
3850
|
+
decision: "deny",
|
|
3851
|
+
code: WORKFLOW_LEASE_UNCOVERED_CODE,
|
|
3852
|
+
reason: `workflow gate (${input.tool}) vetoed: plan "${input.uncoveredPlanId}" is InProgress without execution_lease coverage (workflowGate: hard — no writable fan-out until the plan is recovered)`
|
|
3853
|
+
};
|
|
3854
|
+
}
|
|
3855
|
+
return {
|
|
3856
|
+
decision: "warn",
|
|
3857
|
+
code: WORKFLOW_LEASE_UNCOVERED_CODE,
|
|
3858
|
+
reason: `plan "${input.uncoveredPlanId}" is InProgress without execution_lease coverage (workflowGate: warn — advisory only)`
|
|
3859
|
+
};
|
|
3860
|
+
}
|
|
3861
|
+
if (input.metaName === undefined) {
|
|
3862
|
+
return { decision: "allow" };
|
|
3863
|
+
}
|
|
3864
|
+
const name = input.metaName;
|
|
3865
|
+
if (!workflowNameUnknown(config, name))
|
|
3866
|
+
return { decision: "allow" };
|
|
3867
|
+
switch (mode) {
|
|
3868
|
+
case "warn":
|
|
3869
|
+
return {
|
|
3870
|
+
decision: "warn",
|
|
3871
|
+
code: WORKFLOW_NAME_UNKNOWN_CODE,
|
|
3872
|
+
reason: `workflow name "${name}" is not in the workflowNames allowlist (workflowGate: warn — advisory only)`
|
|
3873
|
+
};
|
|
3874
|
+
case "hard":
|
|
3875
|
+
return {
|
|
3876
|
+
decision: "deny",
|
|
3877
|
+
code: WORKFLOW_NAME_UNKNOWN_CODE,
|
|
3878
|
+
reason: `workflow gate (${input.tool}) vetoed: workflow name "${name}" is not in the workflowNames allowlist (workflowGate: hard)`
|
|
3879
|
+
};
|
|
3880
|
+
case "ask": {
|
|
3881
|
+
const cached = cache.get(name);
|
|
3882
|
+
if (cached === "allow")
|
|
3883
|
+
return { decision: "allow" };
|
|
3884
|
+
if (cached === "deny") {
|
|
3885
|
+
return {
|
|
3886
|
+
decision: "deny",
|
|
3887
|
+
code: WORKFLOW_NAME_UNKNOWN_CODE,
|
|
3888
|
+
reason: `workflow gate (${input.tool}) vetoed: workflow name "${name}" was denied earlier in this session (workflowGate: ask — cached decision)`
|
|
3889
|
+
};
|
|
3890
|
+
}
|
|
3891
|
+
cache.markAsked(name);
|
|
3892
|
+
return {
|
|
3893
|
+
decision: "ask",
|
|
3894
|
+
code: WORKFLOW_NAME_UNKNOWN_CODE,
|
|
3895
|
+
reason: `workflow name "${name}" is not in the workflowNames allowlist (workflowGate: ask — first-seen; approve to allow this session, or deny to veto; the decision is cached)`
|
|
3896
|
+
};
|
|
3897
|
+
}
|
|
3898
|
+
}
|
|
3899
|
+
}
|
|
3900
|
+
|
|
3901
|
+
// src/gates/dispatch.ts
|
|
3748
3902
|
var DISPATCH_LOGGER = "mstar/dispatch-gate";
|
|
3749
|
-
var DEFAULT_DISPATCH_TOOLS = ["subagent"];
|
|
3903
|
+
var DEFAULT_DISPATCH_TOOLS = ["subagent", "subagent_fork"];
|
|
3904
|
+
var DEFAULT_WORKFLOW_TOOLS = ["workflow", "ralph"];
|
|
3750
3905
|
var ASSIGNMENT_HEADING_RE2 = /^#{1,6}\s+Assignment\s*$/m;
|
|
3751
3906
|
var ASSIGNMENT_FIELD_RE2 = /^[ \t]*(?:[-*][ \t]+)?\*{0,2}(Execute as|Delegation|Task category)\*{0,2}[ \t]*:[ \t]*(\S.*)$/gm;
|
|
3752
3907
|
function isAssignmentShaped2(assignmentText) {
|
|
@@ -3931,6 +4086,30 @@ function worktreeL1Violations(harnessDir, header) {
|
|
|
3931
4086
|
}
|
|
3932
4087
|
return l1PreDispatchCheck({ controlWorktreePath, leaseWorktreePath, leaseWorkingBranch, planId }).violations;
|
|
3933
4088
|
}
|
|
4089
|
+
function writableFanOutUncovered(harnessDir) {
|
|
4090
|
+
if (harnessDir === null)
|
|
4091
|
+
return { unreadable: false };
|
|
4092
|
+
const statusPath = join6(harnessDir, STATUS_FILE);
|
|
4093
|
+
if (!existsSync9(statusPath))
|
|
4094
|
+
return { unreadable: false };
|
|
4095
|
+
let doc;
|
|
4096
|
+
try {
|
|
4097
|
+
doc = readJson(statusPath);
|
|
4098
|
+
} catch {
|
|
4099
|
+
return { unreadable: true };
|
|
4100
|
+
}
|
|
4101
|
+
if (!Array.isArray(doc.plans))
|
|
4102
|
+
return { unreadable: true };
|
|
4103
|
+
for (const raw of doc.plans) {
|
|
4104
|
+
const row = asRecord(raw);
|
|
4105
|
+
if (row === undefined || row.status !== "InProgress")
|
|
4106
|
+
continue;
|
|
4107
|
+
const planId = typeof row.id === "string" && row.id !== "" ? row.id : typeof row.plan_id === "string" && row.plan_id !== "" ? row.plan_id : "in-progress-plan";
|
|
4108
|
+
if (!verifyPlanExecutionLease(row, planId).ok)
|
|
4109
|
+
return { uncoveredPlanId: planId, unreadable: false };
|
|
4110
|
+
}
|
|
4111
|
+
return { unreadable: false };
|
|
4112
|
+
}
|
|
3934
4113
|
function dispatchGateCore(config, harnessDir, prompt) {
|
|
3935
4114
|
const header = assignmentHeaderRegion(prompt);
|
|
3936
4115
|
const violations = [...worktreeL2Violations(header), ...worktreeL1Violations(harnessDir, header)];
|
|
@@ -3938,8 +4117,96 @@ function dispatchGateCore(config, harnessDir, prompt) {
|
|
|
3938
4117
|
violations.push(...composeDispatchGate(header, { agent: config.dispatchBinding ?? "", writable }).violations);
|
|
3939
4118
|
return { violations, writable };
|
|
3940
4119
|
}
|
|
4120
|
+
function workflowGateInputOf(exec) {
|
|
4121
|
+
const args = asRecord(exec.arguments);
|
|
4122
|
+
if (args === undefined)
|
|
4123
|
+
return;
|
|
4124
|
+
if (exec.name === "ralph") {
|
|
4125
|
+
const objective = typeof args.objective === "string" ? args.objective : undefined;
|
|
4126
|
+
if (objective === undefined)
|
|
4127
|
+
return;
|
|
4128
|
+
return { tool: exec.name, objective, exec };
|
|
4129
|
+
}
|
|
4130
|
+
if (exec.name === "workflow") {
|
|
4131
|
+
const meta = asRecord(args.meta);
|
|
4132
|
+
const metaName = typeof meta?.name === "string" ? normalizeWorkflowName(meta.name) : undefined;
|
|
4133
|
+
if (meta === undefined || metaName === undefined || metaName === "")
|
|
4134
|
+
return;
|
|
4135
|
+
return { tool: exec.name, metaName, exec };
|
|
4136
|
+
}
|
|
4137
|
+
return;
|
|
4138
|
+
}
|
|
4139
|
+
function gateWorkflow(ctx, harnessDir, config, adapter, exec) {
|
|
4140
|
+
const toolName = exec.name;
|
|
4141
|
+
if (!DEFAULT_WORKFLOW_TOOLS.includes(toolName))
|
|
4142
|
+
return;
|
|
4143
|
+
const mode = config.workflowGate ?? "warn";
|
|
4144
|
+
if (mode === "off")
|
|
4145
|
+
return;
|
|
4146
|
+
const input = workflowGateInputOf(exec);
|
|
4147
|
+
if (input === undefined) {
|
|
4148
|
+
ctx.logger(DISPATCH_LOGGER).warn(`workflow gate (${toolName}) (fail-open): malformed arguments — no ${toolName === "ralph" ? "objective" : "meta"} to gate on; call allowed`);
|
|
4149
|
+
return;
|
|
4150
|
+
}
|
|
4151
|
+
const pb = writableFanOutUncovered(harnessDir);
|
|
4152
|
+
if (pb.unreadable) {
|
|
4153
|
+
const statusLabel = harnessDir === null ? STATUS_FILE : join6(harnessDir, STATUS_FILE);
|
|
4154
|
+
ctx.logger(DISPATCH_LOGGER).warn(`workflow gate (${toolName}) (fail-open): cannot read ${statusLabel} — P-b lease attribution degraded; call allowed`);
|
|
4155
|
+
}
|
|
4156
|
+
const verdict = workflowPolicy(config, adapter.workflowAskCache, {
|
|
4157
|
+
...input,
|
|
4158
|
+
...pb.uncoveredPlanId !== undefined ? { uncoveredPlanId: pb.uncoveredPlanId } : {}
|
|
4159
|
+
});
|
|
4160
|
+
const recordVerdict = (v, code) => {
|
|
4161
|
+
if (harnessDir === null)
|
|
4162
|
+
return;
|
|
4163
|
+
adapter.recordWorkflowVerdict({
|
|
4164
|
+
harnessDir,
|
|
4165
|
+
exec,
|
|
4166
|
+
tool: input.tool,
|
|
4167
|
+
...input.metaName !== undefined ? { workflow: input.metaName } : {},
|
|
4168
|
+
...input.objective !== undefined ? { objective: input.objective } : {},
|
|
4169
|
+
mode,
|
|
4170
|
+
verdict: v,
|
|
4171
|
+
...code !== undefined ? { code } : {}
|
|
4172
|
+
});
|
|
4173
|
+
};
|
|
4174
|
+
switch (verdict.decision) {
|
|
4175
|
+
case "allow":
|
|
4176
|
+
recordVerdict("ok");
|
|
4177
|
+
return;
|
|
4178
|
+
case "warn":
|
|
4179
|
+
ctx.logger(DISPATCH_LOGGER).warn(`workflow gate (${toolName}) (advisory): ${verdict.reason}`);
|
|
4180
|
+
ctx.emit("mstar/dispatch-gate", {
|
|
4181
|
+
tool: toolName,
|
|
4182
|
+
role: "",
|
|
4183
|
+
result: {
|
|
4184
|
+
ok: false,
|
|
4185
|
+
violations: [{
|
|
4186
|
+
ok: false,
|
|
4187
|
+
severity: "medium",
|
|
4188
|
+
code: verdict.code,
|
|
4189
|
+
message: verdict.reason
|
|
4190
|
+
}]
|
|
4191
|
+
},
|
|
4192
|
+
hard: false
|
|
4193
|
+
});
|
|
4194
|
+
recordVerdict("advisory", verdict.code);
|
|
4195
|
+
return;
|
|
4196
|
+
case "ask":
|
|
4197
|
+
recordVerdict("ask", verdict.code);
|
|
4198
|
+
return { kind: "ask", reason: verdict.reason };
|
|
4199
|
+
case "deny":
|
|
4200
|
+
recordVerdict("denied", verdict.code);
|
|
4201
|
+
ctx.logger(DISPATCH_LOGGER).error(`workflow gate (${toolName}) vetoed: ${verdict.reason}`);
|
|
4202
|
+
return { kind: "deny", reason: verdict.reason };
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
3941
4205
|
function gateDispatch(ctx, harnessDir, config, adapter, exec) {
|
|
3942
4206
|
const toolName = exec.name;
|
|
4207
|
+
const workflowDecision = gateWorkflow(ctx, harnessDir, config, adapter, exec);
|
|
4208
|
+
if (workflowDecision !== undefined)
|
|
4209
|
+
return workflowDecision;
|
|
3943
4210
|
if (!(config.dispatchTools ?? [...DEFAULT_DISPATCH_TOOLS]).includes(toolName))
|
|
3944
4211
|
return;
|
|
3945
4212
|
const args = asRecord(exec.arguments);
|
|
@@ -4002,6 +4269,19 @@ var AGENT_FLOW_FILE = "agent-flow.jsonl";
|
|
|
4002
4269
|
var AGENT_FLOW_MAX_EVENTS = 500;
|
|
4003
4270
|
var AGENT_FLOW_DEFAULT_LIMIT = 50;
|
|
4004
4271
|
var AGENT_FLOW_SIZE_GATE_BYTES = 64 * 1024;
|
|
4272
|
+
var WORKFLOW_LEDGER_MAX_ID_LENGTH = 512;
|
|
4273
|
+
var WORKFLOW_LEDGER_MAX_LABEL_LENGTH = 512;
|
|
4274
|
+
var WORKFLOW_LEDGER_MAX_NAME_LENGTH = 1024;
|
|
4275
|
+
var WORKFLOW_LEDGER_TRUNCATION_MARKER = "…";
|
|
4276
|
+
var WORKFLOW_LEDGER_MAX_SEQ = 2 ** 31;
|
|
4277
|
+
function truncateLedgerField(value, cap) {
|
|
4278
|
+
const cps = Array.from(value);
|
|
4279
|
+
if (cps.length <= cap)
|
|
4280
|
+
return value;
|
|
4281
|
+
const keep = cap - WORKFLOW_LEDGER_TRUNCATION_MARKER.length;
|
|
4282
|
+
const prefix = keep > 0 ? cps.slice(0, keep).join("") : "";
|
|
4283
|
+
return prefix + WORKFLOW_LEDGER_TRUNCATION_MARKER;
|
|
4284
|
+
}
|
|
4005
4285
|
var AGENT_FLOW_LOGGER = "mstar/agent-flow";
|
|
4006
4286
|
var SETTLE_SEAM = "tools/post-execute";
|
|
4007
4287
|
var SETTLE_SEAM_PAIRING_NOTE = `settle seam "${SETTLE_SEAM}" IS part of the verified dsh-tools registry surface (runPostExecute dispatches it for every tool call) — foreground dispatch calls settle here, background subagents settle via ctx.jobs.onJobDone pairing; only UNPAIRED payloads (non-dispatch tools, calls outside the apply-scoped pairing window) stay dispatch-only — never a fabricated settle`;
|
|
@@ -4140,13 +4420,53 @@ function recordSettle(input) {
|
|
|
4140
4420
|
log("error", `settle record failed (contained): ${errorMessage(error)}`);
|
|
4141
4421
|
}
|
|
4142
4422
|
}
|
|
4423
|
+
function recordWorkflowEvent(input) {
|
|
4424
|
+
try {
|
|
4425
|
+
appendEvent(input.harnessDir, input.event);
|
|
4426
|
+
try {
|
|
4427
|
+
invalidator?.(input.harnessDir);
|
|
4428
|
+
} catch (error) {
|
|
4429
|
+
log("error", `catalog invalidation failed (contained): ${errorMessage(error)}`);
|
|
4430
|
+
}
|
|
4431
|
+
return true;
|
|
4432
|
+
} catch (error) {
|
|
4433
|
+
log("error", `workflow record failed (contained — the workflow run proceeds): ${errorMessage(error)}`);
|
|
4434
|
+
return false;
|
|
4435
|
+
}
|
|
4436
|
+
}
|
|
4437
|
+
function recordWorkflowVerdict(input) {
|
|
4438
|
+
try {
|
|
4439
|
+
const agent = input.exec !== undefined ? agentOfExec(input.exec) : undefined;
|
|
4440
|
+
const event = {
|
|
4441
|
+
v: 1,
|
|
4442
|
+
ts: Date.now(),
|
|
4443
|
+
kind: "workflow-verdict",
|
|
4444
|
+
...agent !== undefined ? { agent } : {},
|
|
4445
|
+
tool: input.tool,
|
|
4446
|
+
...input.workflow !== undefined && input.workflow !== "" ? { workflow: truncateLedgerField(input.workflow, WORKFLOW_LEDGER_MAX_NAME_LENGTH) } : {},
|
|
4447
|
+
...input.objective !== undefined && input.objective !== "" ? { objective: truncateLedgerField(normalizeWorkflowName(input.objective), WORKFLOW_LEDGER_MAX_NAME_LENGTH) } : {},
|
|
4448
|
+
mode: input.mode,
|
|
4449
|
+
verdict: input.verdict,
|
|
4450
|
+
...input.code !== undefined && input.code !== "" ? { code: input.code } : {}
|
|
4451
|
+
};
|
|
4452
|
+
appendEvent(input.harnessDir, event);
|
|
4453
|
+
try {
|
|
4454
|
+
invalidator?.(input.harnessDir);
|
|
4455
|
+
} catch (error) {
|
|
4456
|
+
log("error", `catalog invalidation failed (contained): ${errorMessage(error)}`);
|
|
4457
|
+
}
|
|
4458
|
+
} catch (error) {
|
|
4459
|
+
log("error", `workflow verdict record failed (contained — the gate proceeds): ${errorMessage(error)}`);
|
|
4460
|
+
}
|
|
4461
|
+
}
|
|
4143
4462
|
function eventFromUnknown(value) {
|
|
4144
4463
|
const rec = asRecord(value);
|
|
4145
4464
|
if (rec === undefined || rec.v !== 1 || typeof rec.ts !== "number" || !Number.isFinite(rec.ts))
|
|
4146
4465
|
return;
|
|
4147
4466
|
const kind = rec.kind;
|
|
4148
|
-
if (kind !== "dispatch" && kind !== "settle")
|
|
4467
|
+
if (kind !== "dispatch" && kind !== "settle" && kind !== "workflow-verdict" && kind !== "workflow-run" && kind !== "workflow-agent" && kind !== "workflow-run-end") {
|
|
4149
4468
|
return;
|
|
4469
|
+
}
|
|
4150
4470
|
const agent = typeof rec.agent === "string" && rec.agent !== "" ? rec.agent : undefined;
|
|
4151
4471
|
if (kind === "dispatch") {
|
|
4152
4472
|
if (typeof rec.role !== "string" || typeof rec.hard !== "boolean")
|
|
@@ -4167,19 +4487,92 @@ function eventFromUnknown(value) {
|
|
|
4167
4487
|
hard: rec.hard
|
|
4168
4488
|
};
|
|
4169
4489
|
}
|
|
4170
|
-
|
|
4171
|
-
|
|
4490
|
+
if (kind === "settle") {
|
|
4491
|
+
const outcome = rec.outcome;
|
|
4492
|
+
if (outcome !== "ok" && outcome !== "error" && outcome !== "denied")
|
|
4493
|
+
return;
|
|
4494
|
+
return {
|
|
4495
|
+
v: 1,
|
|
4496
|
+
ts: rec.ts,
|
|
4497
|
+
kind: "settle",
|
|
4498
|
+
...agent !== undefined ? { agent } : {},
|
|
4499
|
+
outcome,
|
|
4500
|
+
...typeof rec.durationMs === "number" && Number.isFinite(rec.durationMs) ? { durationMs: rec.durationMs } : {},
|
|
4501
|
+
...typeof rec.role === "string" ? { role: rec.role } : {},
|
|
4502
|
+
...typeof rec.planId === "string" && rec.planId !== "" ? { planId: rec.planId } : {},
|
|
4503
|
+
...typeof rec.taskId === "string" && rec.taskId !== "" ? { taskId: rec.taskId } : {}
|
|
4504
|
+
};
|
|
4505
|
+
}
|
|
4506
|
+
if (kind === "workflow-verdict") {
|
|
4507
|
+
if (rec.tool !== "workflow" && rec.tool !== "ralph")
|
|
4508
|
+
return;
|
|
4509
|
+
const mode = rec.mode;
|
|
4510
|
+
if (mode !== "off" && mode !== "warn" && mode !== "ask" && mode !== "hard")
|
|
4511
|
+
return;
|
|
4512
|
+
const verdict = rec.verdict;
|
|
4513
|
+
if (verdict !== "ok" && verdict !== "advisory" && verdict !== "denied" && verdict !== "ask")
|
|
4514
|
+
return;
|
|
4515
|
+
if (rec.tool === "workflow") {
|
|
4516
|
+
if (typeof rec.workflow !== "string" || rec.workflow === "")
|
|
4517
|
+
return;
|
|
4518
|
+
} else if (typeof rec.objective !== "string" || rec.objective === "") {
|
|
4519
|
+
return;
|
|
4520
|
+
}
|
|
4521
|
+
return {
|
|
4522
|
+
v: 1,
|
|
4523
|
+
ts: rec.ts,
|
|
4524
|
+
kind: "workflow-verdict",
|
|
4525
|
+
...agent !== undefined ? { agent } : {},
|
|
4526
|
+
tool: rec.tool,
|
|
4527
|
+
...typeof rec.workflow === "string" && rec.workflow !== "" ? { workflow: truncateLedgerField(rec.workflow, WORKFLOW_LEDGER_MAX_NAME_LENGTH) } : {},
|
|
4528
|
+
...typeof rec.objective === "string" && rec.objective !== "" ? { objective: truncateLedgerField(rec.objective, WORKFLOW_LEDGER_MAX_NAME_LENGTH) } : {},
|
|
4529
|
+
mode,
|
|
4530
|
+
verdict,
|
|
4531
|
+
...typeof rec.code === "string" && rec.code !== "" ? { code: rec.code } : {}
|
|
4532
|
+
};
|
|
4533
|
+
}
|
|
4534
|
+
if (typeof rec.runId !== "string" || rec.runId === "" || rec.runId.length > WORKFLOW_LEDGER_MAX_ID_LENGTH)
|
|
4535
|
+
return;
|
|
4536
|
+
if (kind === "workflow-run") {
|
|
4537
|
+
if (typeof rec.name !== "string" || rec.name === "")
|
|
4538
|
+
return;
|
|
4539
|
+
return {
|
|
4540
|
+
v: 1,
|
|
4541
|
+
ts: rec.ts,
|
|
4542
|
+
kind: "workflow-run",
|
|
4543
|
+
...agent !== undefined ? { agent } : {},
|
|
4544
|
+
runId: rec.runId,
|
|
4545
|
+
name: truncateLedgerField(rec.name, WORKFLOW_LEDGER_MAX_NAME_LENGTH)
|
|
4546
|
+
};
|
|
4547
|
+
}
|
|
4548
|
+
if (kind === "workflow-agent") {
|
|
4549
|
+
if (typeof rec.seq !== "number" || !Number.isInteger(rec.seq) || rec.seq < 1 || rec.seq >= WORKFLOW_LEDGER_MAX_SEQ) {
|
|
4550
|
+
return;
|
|
4551
|
+
}
|
|
4552
|
+
if (typeof rec.label !== "string" || rec.label === "")
|
|
4553
|
+
return;
|
|
4554
|
+
if (typeof rec.childId !== "string" || rec.childId === "" || rec.childId.length > WORKFLOW_LEDGER_MAX_ID_LENGTH)
|
|
4555
|
+
return;
|
|
4556
|
+
return {
|
|
4557
|
+
v: 1,
|
|
4558
|
+
ts: rec.ts,
|
|
4559
|
+
kind: "workflow-agent",
|
|
4560
|
+
runId: rec.runId,
|
|
4561
|
+
seq: rec.seq,
|
|
4562
|
+
label: truncateLedgerField(rec.label, WORKFLOW_LEDGER_MAX_LABEL_LENGTH),
|
|
4563
|
+
...typeof rec.phase === "string" ? { phase: truncateLedgerField(rec.phase, WORKFLOW_LEDGER_MAX_LABEL_LENGTH) } : {},
|
|
4564
|
+
childId: rec.childId
|
|
4565
|
+
};
|
|
4566
|
+
}
|
|
4567
|
+
const stopReason = rec.stopReason;
|
|
4568
|
+
if (stopReason !== "completed" && stopReason !== "cancelled" && stopReason !== "error")
|
|
4172
4569
|
return;
|
|
4173
4570
|
return {
|
|
4174
4571
|
v: 1,
|
|
4175
4572
|
ts: rec.ts,
|
|
4176
|
-
kind: "
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
...typeof rec.durationMs === "number" && Number.isFinite(rec.durationMs) ? { durationMs: rec.durationMs } : {},
|
|
4180
|
-
...typeof rec.role === "string" ? { role: rec.role } : {},
|
|
4181
|
-
...typeof rec.planId === "string" && rec.planId !== "" ? { planId: rec.planId } : {},
|
|
4182
|
-
...typeof rec.taskId === "string" && rec.taskId !== "" ? { taskId: rec.taskId } : {}
|
|
4573
|
+
kind: "workflow-run-end",
|
|
4574
|
+
runId: rec.runId,
|
|
4575
|
+
stopReason
|
|
4183
4576
|
};
|
|
4184
4577
|
}
|
|
4185
4578
|
function eventView(event) {
|
|
@@ -4196,6 +4589,65 @@ function eventView(event) {
|
|
|
4196
4589
|
...event.hard !== undefined ? { hard: event.hard } : {}
|
|
4197
4590
|
};
|
|
4198
4591
|
}
|
|
4592
|
+
if (event.kind === "workflow-run") {
|
|
4593
|
+
return {
|
|
4594
|
+
ts: event.ts,
|
|
4595
|
+
kind: "workflow-run",
|
|
4596
|
+
agent: event.agent ?? null,
|
|
4597
|
+
role: "",
|
|
4598
|
+
planId: null,
|
|
4599
|
+
taskId: null,
|
|
4600
|
+
taskCategory: null,
|
|
4601
|
+
runId: event.runId,
|
|
4602
|
+
name: event.name
|
|
4603
|
+
};
|
|
4604
|
+
}
|
|
4605
|
+
if (event.kind === "workflow-agent") {
|
|
4606
|
+
return {
|
|
4607
|
+
ts: event.ts,
|
|
4608
|
+
kind: "workflow-agent",
|
|
4609
|
+
agent: null,
|
|
4610
|
+
role: "",
|
|
4611
|
+
planId: null,
|
|
4612
|
+
taskId: null,
|
|
4613
|
+
taskCategory: null,
|
|
4614
|
+
runId: event.runId,
|
|
4615
|
+
seq: event.seq,
|
|
4616
|
+
label: event.label,
|
|
4617
|
+
...event.phase !== undefined ? { phase: event.phase } : {},
|
|
4618
|
+
childId: event.childId
|
|
4619
|
+
};
|
|
4620
|
+
}
|
|
4621
|
+
if (event.kind === "workflow-run-end") {
|
|
4622
|
+
return {
|
|
4623
|
+
ts: event.ts,
|
|
4624
|
+
kind: "workflow-run-end",
|
|
4625
|
+
agent: null,
|
|
4626
|
+
role: "",
|
|
4627
|
+
planId: null,
|
|
4628
|
+
taskId: null,
|
|
4629
|
+
taskCategory: null,
|
|
4630
|
+
runId: event.runId,
|
|
4631
|
+
stopReason: event.stopReason
|
|
4632
|
+
};
|
|
4633
|
+
}
|
|
4634
|
+
if (event.kind === "workflow-verdict") {
|
|
4635
|
+
return {
|
|
4636
|
+
ts: event.ts,
|
|
4637
|
+
kind: "workflow-verdict",
|
|
4638
|
+
agent: event.agent ?? null,
|
|
4639
|
+
role: "",
|
|
4640
|
+
planId: null,
|
|
4641
|
+
taskId: null,
|
|
4642
|
+
taskCategory: null,
|
|
4643
|
+
tool: event.tool,
|
|
4644
|
+
...event.workflow !== undefined ? { workflow: event.workflow } : {},
|
|
4645
|
+
...event.objective !== undefined ? { objective: event.objective } : {},
|
|
4646
|
+
mode: event.mode,
|
|
4647
|
+
verdict: event.verdict,
|
|
4648
|
+
...event.code !== undefined ? { code: event.code } : {}
|
|
4649
|
+
};
|
|
4650
|
+
}
|
|
4199
4651
|
return {
|
|
4200
4652
|
ts: event.ts,
|
|
4201
4653
|
kind: "settle",
|
|
@@ -4212,8 +4664,8 @@ function eventView(event) {
|
|
|
4212
4664
|
function summaryOf(events) {
|
|
4213
4665
|
const counts = new Map;
|
|
4214
4666
|
for (const event of events) {
|
|
4215
|
-
const role = event.kind === "dispatch" ? event.role : "";
|
|
4216
|
-
const outcome = event.kind === "dispatch" ? event.verdict : event.outcome;
|
|
4667
|
+
const role = event.kind === "dispatch" ? event.role : event.kind === "workflow-run" || event.kind === "workflow-agent" || event.kind === "workflow-run-end" || event.kind === "workflow-verdict" ? "workflow" : "";
|
|
4668
|
+
const outcome = event.kind === "dispatch" ? event.verdict : event.kind === "workflow-verdict" ? event.verdict : event.kind === "settle" ? event.outcome : event.kind;
|
|
4217
4669
|
const key = `${role}\x00${outcome}`;
|
|
4218
4670
|
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
4219
4671
|
}
|
|
@@ -4832,6 +5284,18 @@ function stripFrontmatter(text) {
|
|
|
4832
5284
|
}
|
|
4833
5285
|
return text;
|
|
4834
5286
|
}
|
|
5287
|
+
var EPHEMERAL_CODES = {
|
|
5288
|
+
"task-artifact": "skill.ephemeral.task-artifact",
|
|
5289
|
+
"sdd-deeplink": "skill.ephemeral.sdd-deeplink"
|
|
5290
|
+
};
|
|
5291
|
+
function ephemeralViolation(citation) {
|
|
5292
|
+
return {
|
|
5293
|
+
ok: false,
|
|
5294
|
+
severity: "medium",
|
|
5295
|
+
code: EPHEMERAL_CODES[citation.kind],
|
|
5296
|
+
message: `ephemeral citation \`${citation.match}\` on line ${citation.line} — durable skill text must reference in-repo artifacts only (knowledge conventions/skill-content-porting-discipline.md §3)`
|
|
5297
|
+
};
|
|
5298
|
+
}
|
|
4835
5299
|
function lintSkillDoc(doc) {
|
|
4836
5300
|
const violations = [];
|
|
4837
5301
|
const frontmatter = lintSkillFrontmatter(doc);
|
|
@@ -4840,6 +5304,7 @@ function lintSkillDoc(doc) {
|
|
|
4840
5304
|
const body = lintFiveQuestion(stripFrontmatter(doc));
|
|
4841
5305
|
if (!body.ok)
|
|
4842
5306
|
violations.push(...body.violations);
|
|
5307
|
+
violations.push(...findEphemeralCitations(doc).map(ephemeralViolation));
|
|
4843
5308
|
return violations.length === 0 ? { ok: true, violations } : { ok: false, violations };
|
|
4844
5309
|
}
|
|
4845
5310
|
function lintSkillWrite(doc, options) {
|
|
@@ -5512,12 +5977,14 @@ class DshHostAdapter extends Service2 {
|
|
|
5512
5977
|
resolver;
|
|
5513
5978
|
config;
|
|
5514
5979
|
pairing;
|
|
5980
|
+
workflowAskCache;
|
|
5515
5981
|
logSink;
|
|
5516
5982
|
constructor(ctx, options) {
|
|
5517
5983
|
super(ctx, "dshHostAdapter");
|
|
5518
5984
|
this.resolver = options.resolver;
|
|
5519
5985
|
this.config = options.config;
|
|
5520
5986
|
this.pairing = options.pairing;
|
|
5987
|
+
this.workflowAskCache = options.workflowAskCache ?? new WorkflowAskCache;
|
|
5521
5988
|
this.logSink = options.log ?? ((level, msg) => {
|
|
5522
5989
|
const logger = ctx.logger(HOST_LOGGER);
|
|
5523
5990
|
if (level === "warn")
|
|
@@ -5558,6 +6025,9 @@ class DshHostAdapter extends Service2 {
|
|
|
5558
6025
|
}
|
|
5559
6026
|
return { ok: violations.length === 0, violations };
|
|
5560
6027
|
}
|
|
6028
|
+
recordWorkflowVerdict(input) {
|
|
6029
|
+
recordWorkflowVerdict(input);
|
|
6030
|
+
}
|
|
5561
6031
|
async beforeStatusWrite(path, doc) {
|
|
5562
6032
|
const gate2 = doc !== undefined ? validateStatusValue(doc) : this.statusGate(path);
|
|
5563
6033
|
if (!gate2.ok) {
|
|
@@ -5578,28 +6048,91 @@ class DshHostAdapter extends Service2 {
|
|
|
5578
6048
|
}
|
|
5579
6049
|
}
|
|
5580
6050
|
|
|
5581
|
-
// src/
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
6051
|
+
// src/gates/agent-personas.ts
|
|
6052
|
+
import { readFileSync as readFileSync12, readdirSync as readdirSync7, statSync as statSync4 } from "node:fs";
|
|
6053
|
+
import { join as join16, resolve as resolve14, sep as sep4 } from "node:path";
|
|
6054
|
+
var ROLE_ID_PATTERN = /^[a-z0-9-]{1,32}$/;
|
|
6055
|
+
var defaultCache = new Map;
|
|
6056
|
+
function personaFor(roleId, lookup, warn) {
|
|
6057
|
+
const configured = lookup.rolePersonas?.[roleId];
|
|
6058
|
+
if (configured !== undefined && configured !== "")
|
|
6059
|
+
return { text: configured, source: "config" };
|
|
6060
|
+
if (lookup.agentsDir === undefined)
|
|
6061
|
+
return;
|
|
6062
|
+
const text = defaultFromMirror(lookup.agentsDir, roleId, warn);
|
|
6063
|
+
if (text === undefined || text.trim() === "")
|
|
6064
|
+
return;
|
|
6065
|
+
return { text, source: "default" };
|
|
6066
|
+
}
|
|
6067
|
+
function subagentRoleIds(agentsDir) {
|
|
6068
|
+
let names;
|
|
5585
6069
|
try {
|
|
5586
|
-
|
|
5587
|
-
return existsSync16(dir) ? dir : undefined;
|
|
6070
|
+
names = readdirSync7(agentsDir);
|
|
5588
6071
|
} catch {
|
|
5589
|
-
return;
|
|
6072
|
+
return [];
|
|
6073
|
+
}
|
|
6074
|
+
const ids = [];
|
|
6075
|
+
for (const name of names) {
|
|
6076
|
+
if (!name.endsWith(".md"))
|
|
6077
|
+
continue;
|
|
6078
|
+
const roleId = name.slice(0, -3);
|
|
6079
|
+
let content;
|
|
6080
|
+
try {
|
|
6081
|
+
content = readFileSync12(join16(agentsDir, name), "utf8");
|
|
6082
|
+
} catch {
|
|
6083
|
+
continue;
|
|
6084
|
+
}
|
|
6085
|
+
const mode = parseShellFrontmatter(content).mode;
|
|
6086
|
+
if (mode === undefined || mode === "subagent")
|
|
6087
|
+
ids.push(roleId);
|
|
5590
6088
|
}
|
|
6089
|
+
return ids.sort();
|
|
5591
6090
|
}
|
|
5592
|
-
function
|
|
5593
|
-
|
|
5594
|
-
|
|
6091
|
+
function defaultFromMirror(agentsDir, roleId, warn) {
|
|
6092
|
+
if (!ROLE_ID_PATTERN.test(roleId))
|
|
6093
|
+
return;
|
|
6094
|
+
const root = resolve14(agentsDir);
|
|
6095
|
+
const file = resolve14(agentsDir, `${roleId}.md`);
|
|
6096
|
+
if (!file.startsWith(root + sep4))
|
|
6097
|
+
return;
|
|
6098
|
+
let stat;
|
|
6099
|
+
try {
|
|
6100
|
+
stat = statSync4(file);
|
|
6101
|
+
} catch {
|
|
6102
|
+
return;
|
|
6103
|
+
}
|
|
6104
|
+
if (!stat.isFile())
|
|
6105
|
+
return;
|
|
6106
|
+
const hit = defaultCache.get(file);
|
|
6107
|
+
if (hit !== undefined && hit.mtimeMs === stat.mtimeMs)
|
|
6108
|
+
return hit.text;
|
|
6109
|
+
let content;
|
|
6110
|
+
try {
|
|
6111
|
+
content = readFileSync12(file, "utf8");
|
|
6112
|
+
} catch {
|
|
6113
|
+
return;
|
|
6114
|
+
}
|
|
6115
|
+
const text = extractShellPersona(content, roleId, warn);
|
|
6116
|
+
defaultCache.set(file, { mtimeMs: stat.mtimeMs, text });
|
|
6117
|
+
return text;
|
|
5595
6118
|
}
|
|
5596
|
-
function
|
|
5597
|
-
|
|
6119
|
+
function extractShellPersona(content, roleId, warn) {
|
|
6120
|
+
const parsed = parseShellFrontmatter(content);
|
|
6121
|
+
const description = parsed.description;
|
|
6122
|
+
if (description === undefined || description.trim() === "")
|
|
6123
|
+
return;
|
|
6124
|
+
if (parsed.mode !== undefined && parsed.mode !== "subagent")
|
|
6125
|
+
return;
|
|
6126
|
+
if (PERSONA_INTERPOLATION_HAZARD.test(description)) {
|
|
6127
|
+
warn?.(`harness-agents default skipped for role '${roleId}' — description contains a "{{" paired with a later "}}" (dsh system-prompt strict interpolation renders persona text and throws on unknown or malformed references — use single braces or reword)`);
|
|
6128
|
+
return;
|
|
6129
|
+
}
|
|
6130
|
+
return description;
|
|
5598
6131
|
}
|
|
5599
|
-
function
|
|
6132
|
+
function parseShellFrontmatter(content) {
|
|
5600
6133
|
const lines = content.replace(/^\uFEFF/, "").split(/\r?\n/);
|
|
5601
6134
|
if (lines.length === 0 || lines[0].trim() !== "---")
|
|
5602
|
-
return;
|
|
6135
|
+
return {};
|
|
5603
6136
|
let end = -1;
|
|
5604
6137
|
for (let i = 1;i < lines.length; i++) {
|
|
5605
6138
|
if (lines[i].trim() === "---") {
|
|
@@ -5608,34 +6141,1169 @@ function parseCommandMarkdown(content) {
|
|
|
5608
6141
|
}
|
|
5609
6142
|
}
|
|
5610
6143
|
if (end === -1)
|
|
5611
|
-
return;
|
|
5612
|
-
const frontmatter = lines.slice(1, end)
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
6144
|
+
return {};
|
|
6145
|
+
const frontmatter = lines.slice(1, end);
|
|
6146
|
+
const result = {};
|
|
6147
|
+
for (let i = 0;i < frontmatter.length; i++) {
|
|
6148
|
+
const line = frontmatter[i];
|
|
6149
|
+
const key = /^([A-Za-z][A-Za-z0-9_-]*)[ \t]*:[ \t]*(.*)$/.exec(line);
|
|
6150
|
+
if (key === null)
|
|
6151
|
+
continue;
|
|
6152
|
+
const label = key[1];
|
|
6153
|
+
const rest = key[2];
|
|
6154
|
+
if (label === "description") {
|
|
6155
|
+
const block = /^\|[+-]?(?<indent>\d*)[ \t]*$/.exec(rest);
|
|
6156
|
+
result.description = block !== null ? extractBlockScalar(frontmatter, i + 1, block.groups?.indent) : unquote(rest.trim());
|
|
6157
|
+
} else if (label === "mode") {
|
|
6158
|
+
result.mode = unquote(rest.trim());
|
|
6159
|
+
}
|
|
6160
|
+
}
|
|
6161
|
+
return result;
|
|
5626
6162
|
}
|
|
5627
|
-
function
|
|
5628
|
-
const
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
6163
|
+
function extractBlockScalar(frontmatter, start, explicitIndent) {
|
|
6164
|
+
const blockLines = [];
|
|
6165
|
+
for (let i = start;i < frontmatter.length; i++) {
|
|
6166
|
+
const line = frontmatter[i];
|
|
6167
|
+
if (line.trim() === "") {
|
|
6168
|
+
blockLines.push("");
|
|
6169
|
+
continue;
|
|
6170
|
+
}
|
|
6171
|
+
if (/^[ \t]/.test(line)) {
|
|
6172
|
+
blockLines.push(line);
|
|
6173
|
+
continue;
|
|
6174
|
+
}
|
|
6175
|
+
break;
|
|
6176
|
+
}
|
|
6177
|
+
let indent = explicitIndent !== undefined && explicitIndent !== "" ? Number(explicitIndent) : undefined;
|
|
6178
|
+
if (indent === undefined) {
|
|
6179
|
+
const first = blockLines.find((line) => line.trim() !== "");
|
|
6180
|
+
indent = first !== undefined ? /^[ \t]*/.exec(first)[0].length : 0;
|
|
6181
|
+
}
|
|
6182
|
+
return blockLines.map((line) => line.trim() === "" ? "" : line.slice(Math.min(indent, line.length))).join(`
|
|
6183
|
+
`).replace(/\n+$/, "");
|
|
6184
|
+
}
|
|
6185
|
+
function unquote(value) {
|
|
6186
|
+
return value.length >= 2 && value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value;
|
|
6187
|
+
}
|
|
6188
|
+
|
|
6189
|
+
// src/gates/fallbacks-probe.ts
|
|
6190
|
+
var FALLBACKS_ENTRY_NAME = "dsh-llm-fallbacks";
|
|
6191
|
+
function fallbacksService(ctx) {
|
|
6192
|
+
return ctx.get("llm-fallbacks");
|
|
6193
|
+
}
|
|
6194
|
+
function fallbacksEntry(ctx) {
|
|
6195
|
+
const loader = ctx.get("loader");
|
|
6196
|
+
if (loader === undefined)
|
|
6197
|
+
return;
|
|
6198
|
+
for (const entry of loader.entries()) {
|
|
6199
|
+
if (entry.options.name !== FALLBACKS_ENTRY_NAME)
|
|
6200
|
+
continue;
|
|
6201
|
+
if (entry.options.group)
|
|
6202
|
+
continue;
|
|
6203
|
+
if (entry.disabled)
|
|
6204
|
+
continue;
|
|
6205
|
+
return entry;
|
|
6206
|
+
}
|
|
6207
|
+
return;
|
|
6208
|
+
}
|
|
6209
|
+
function fallbacksMounted(ctx) {
|
|
6210
|
+
if (fallbacksService(ctx) !== undefined)
|
|
6211
|
+
return true;
|
|
6212
|
+
const entry = fallbacksEntry(ctx);
|
|
6213
|
+
if (entry === undefined)
|
|
6214
|
+
return false;
|
|
6215
|
+
return entry.fiber !== undefined;
|
|
6216
|
+
}
|
|
6217
|
+
|
|
6218
|
+
// src/gates/fallbacks-decoration.ts
|
|
6219
|
+
var DECORATION_LOGGER = "mstar/subagent-decoration";
|
|
6220
|
+
var PERSONA_SECTION_NAME = "mstar:role-persona";
|
|
6221
|
+
var PERSONA_SECTION_ORDER = 1;
|
|
6222
|
+
var decorationLogSink = () => {};
|
|
6223
|
+
function setDecorationLogger(sink) {
|
|
6224
|
+
const prior = decorationLogSink;
|
|
6225
|
+
decorationLogSink = sink;
|
|
6226
|
+
return prior;
|
|
6227
|
+
}
|
|
6228
|
+
var decorationAgentsDir;
|
|
6229
|
+
var mirrorAbsentDebugged = false;
|
|
6230
|
+
function setDecorationAgentsDir(dir) {
|
|
6231
|
+
const prior = decorationAgentsDir;
|
|
6232
|
+
decorationAgentsDir = dir;
|
|
6233
|
+
mirrorAbsentDebugged = false;
|
|
6234
|
+
return prior;
|
|
6235
|
+
}
|
|
6236
|
+
function seededTaskPrompt(child) {
|
|
6237
|
+
for (const event of child.session.events) {
|
|
6238
|
+
if (event.type !== "user/message")
|
|
6239
|
+
continue;
|
|
6240
|
+
const text = event.data.content.filter((block) => block.type === "text").map((block) => block.text).join(`
|
|
6241
|
+
`);
|
|
6242
|
+
if (text.trim().length > 0)
|
|
6243
|
+
return text;
|
|
6244
|
+
}
|
|
6245
|
+
return;
|
|
6246
|
+
}
|
|
6247
|
+
function decorateSubagentStart(ctx, config, info) {
|
|
6248
|
+
try {
|
|
6249
|
+
const personas = config.rolePersonas;
|
|
6250
|
+
const agentsDir = decorationAgentsDir;
|
|
6251
|
+
if ((personas == null || Object.keys(personas).length === 0) && agentsDir === undefined)
|
|
6252
|
+
return;
|
|
6253
|
+
const agents = ctx.get("agents");
|
|
6254
|
+
if (agents === undefined) {
|
|
6255
|
+
log2("debug", "agents service absent — mstar:role-persona decoration skipped (composition without dsh-agent)");
|
|
6256
|
+
return;
|
|
6257
|
+
}
|
|
6258
|
+
const child = agents.get(info.id);
|
|
6259
|
+
if (child === undefined)
|
|
6260
|
+
return;
|
|
6261
|
+
const taskPrompt = seededTaskPrompt(child);
|
|
6262
|
+
if (taskPrompt === undefined)
|
|
6263
|
+
return;
|
|
6264
|
+
const executeAs = parseAssignmentFields(assignmentHeaderRegion(taskPrompt)).executeAs;
|
|
6265
|
+
if (executeAs === undefined)
|
|
6266
|
+
return;
|
|
6267
|
+
const persona = personaFor(executeAs, { rolePersonas: config.rolePersonas, agentsDir }, (message) => log2("warn", message));
|
|
6268
|
+
if (persona === undefined) {
|
|
6269
|
+
if (agentsDir === undefined && !mirrorAbsentDebugged) {
|
|
6270
|
+
mirrorAbsentDebugged = true;
|
|
6271
|
+
log2("debug", "harness-agents mirror absent — mstar:role-persona skipped (config-only lookups; mirror defaults unavailable)");
|
|
6272
|
+
}
|
|
6273
|
+
return;
|
|
6274
|
+
}
|
|
6275
|
+
child.ctx.systemPrompt.section({
|
|
6276
|
+
name: PERSONA_SECTION_NAME,
|
|
6277
|
+
order: PERSONA_SECTION_ORDER,
|
|
6278
|
+
text: persona.text
|
|
6279
|
+
});
|
|
6280
|
+
if (fallbacksMounted(ctx)) {
|
|
6281
|
+
log2("info", `dsh-llm-fallbacks mounted (v${fallbacksService(ctx)?.version ?? "unknown"}) — mstar:role-persona injected for role '${executeAs}'`);
|
|
6282
|
+
} else {
|
|
6283
|
+
log2("debug", `dsh-llm-fallbacks unmounted — mstar:role-persona injected from ${persona.source === "config" ? "mstar Config" : "harness-agents default"} for role '${executeAs}' (decoration channel unchanged)`);
|
|
6284
|
+
}
|
|
6285
|
+
} catch (error) {
|
|
6286
|
+
log2("warn", `mstar:role-persona decoration aborted (degraded — subagent dispatch unaffected): ${errorMessage2(error)}`);
|
|
6287
|
+
}
|
|
6288
|
+
}
|
|
6289
|
+
function errorMessage2(error) {
|
|
6290
|
+
return error instanceof Error ? error.message : String(error);
|
|
6291
|
+
}
|
|
6292
|
+
function log2(level, message) {
|
|
6293
|
+
try {
|
|
6294
|
+
decorationLogSink(level, message);
|
|
6295
|
+
} catch {}
|
|
6296
|
+
}
|
|
6297
|
+
|
|
6298
|
+
// src/gates/workflow-ledger.ts
|
|
6299
|
+
import { readFileSync as readFileSync13, renameSync as renameSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
6300
|
+
import { join as join17 } from "node:path";
|
|
6301
|
+
var WORKFLOW_LEDGER_LOGGER = "mstar/workflow-ledger";
|
|
6302
|
+
var TOOL_WORKFLOW_RUN_START = "tool-workflow/run-start";
|
|
6303
|
+
var TOOL_WORKFLOW_AGENT_START = "tool-workflow/agent-start";
|
|
6304
|
+
var TOOL_WORKFLOW_AGENT_END = "tool-workflow/agent-end";
|
|
6305
|
+
var TOOL_WORKFLOW_RUN_END = "tool-workflow/run-end";
|
|
6306
|
+
var DEPTH_ADVISORY_THRESHOLD = 2;
|
|
6307
|
+
var WORKFLOW_LEDGER_WATERMARK_FILE = "workflow-ledger-cursors.json";
|
|
6308
|
+
var WORKFLOW_LEDGER_WATERMARK_MAX_SESSIONS = 256;
|
|
6309
|
+
var workflowLedgerLogSink = () => {};
|
|
6310
|
+
function setWorkflowLedgerLogger(sink) {
|
|
6311
|
+
const prior = workflowLedgerLogSink;
|
|
6312
|
+
workflowLedgerLogSink = sink;
|
|
6313
|
+
return prior;
|
|
6314
|
+
}
|
|
6315
|
+
function log3(level, message) {
|
|
6316
|
+
try {
|
|
6317
|
+
workflowLedgerLogSink(level, message);
|
|
6318
|
+
} catch {}
|
|
6319
|
+
}
|
|
6320
|
+
function errorMessage3(error) {
|
|
6321
|
+
return error instanceof Error ? error.message : String(error);
|
|
6322
|
+
}
|
|
6323
|
+
function sessionIdOf2(session) {
|
|
6324
|
+
const id = session?.id;
|
|
6325
|
+
return typeof id === "string" && id !== "" ? id : undefined;
|
|
6326
|
+
}
|
|
6327
|
+
var watermarkCache = new Map;
|
|
6328
|
+
function loadWatermark(harnessDir) {
|
|
6329
|
+
const cached = watermarkCache.get(harnessDir);
|
|
6330
|
+
if (cached !== undefined)
|
|
6331
|
+
return cached;
|
|
6332
|
+
const watermark = new Map;
|
|
6333
|
+
try {
|
|
6334
|
+
const raw = readFileSync13(join17(harnessDir, WORKFLOW_LEDGER_WATERMARK_FILE), "utf8");
|
|
6335
|
+
const record = asRecord(JSON.parse(raw));
|
|
6336
|
+
const cursors = asRecord(record?.cursors);
|
|
6337
|
+
if (cursors !== undefined) {
|
|
6338
|
+
for (const [sid, next] of Object.entries(cursors)) {
|
|
6339
|
+
if (typeof next === "number" && Number.isInteger(next) && next >= 1 && next < WORKFLOW_LEDGER_MAX_SEQ && sid !== "") {
|
|
6340
|
+
watermark.set(sid, next);
|
|
6341
|
+
}
|
|
6342
|
+
}
|
|
6343
|
+
}
|
|
6344
|
+
} catch (error) {
|
|
6345
|
+
const err = error;
|
|
6346
|
+
if (err?.code !== "ENOENT") {
|
|
6347
|
+
log3("warn", `workflow-ledger watermark unreadable for ${harnessDir} — degrading to in-memory-only (restart re-records): ${errorMessage3(error)}`);
|
|
6348
|
+
}
|
|
6349
|
+
}
|
|
6350
|
+
watermarkCache.set(harnessDir, watermark);
|
|
6351
|
+
return watermark;
|
|
6352
|
+
}
|
|
6353
|
+
function saveWatermark(harnessDir, watermark) {
|
|
6354
|
+
try {
|
|
6355
|
+
const file = join17(harnessDir, WORKFLOW_LEDGER_WATERMARK_FILE);
|
|
6356
|
+
const tmp = `${file}.tmp`;
|
|
6357
|
+
writeFileSync4(tmp, JSON.stringify({ v: 1, cursors: Object.fromEntries(watermark) }));
|
|
6358
|
+
renameSync3(tmp, file);
|
|
6359
|
+
} catch (error) {
|
|
6360
|
+
log3("warn", `workflow-ledger watermark write failed for ${harnessDir} — in-memory only (restart re-records): ${errorMessage3(error)}`);
|
|
6361
|
+
}
|
|
6362
|
+
}
|
|
6363
|
+
function advanceWatermark(harnessDir, sid, nextSeq, isEvictable) {
|
|
6364
|
+
const watermark = loadWatermark(harnessDir);
|
|
6365
|
+
if (!watermark.has(sid) && watermark.size >= WORKFLOW_LEDGER_WATERMARK_MAX_SESSIONS) {
|
|
6366
|
+
let victim;
|
|
6367
|
+
for (const key of watermark.keys()) {
|
|
6368
|
+
if (isEvictable(key)) {
|
|
6369
|
+
victim = key;
|
|
6370
|
+
break;
|
|
6371
|
+
}
|
|
6372
|
+
}
|
|
6373
|
+
victim ??= watermark.keys().next().value;
|
|
6374
|
+
if (victim !== undefined) {
|
|
6375
|
+
watermark.delete(victim);
|
|
6376
|
+
log3("warn", `workflow-ledger watermark capped at ${WORKFLOW_LEDGER_WATERMARK_MAX_SESSIONS} sessions — evicted ${victim} (a restored evicted session re-records; bounded by the cap)`);
|
|
6377
|
+
}
|
|
6378
|
+
}
|
|
6379
|
+
watermark.set(sid, nextSeq);
|
|
6380
|
+
saveWatermark(harnessDir, watermark);
|
|
6381
|
+
}
|
|
6382
|
+
function rowOf(session, envelope) {
|
|
6383
|
+
const record = asRecord(envelope);
|
|
6384
|
+
if (record === undefined)
|
|
6385
|
+
return;
|
|
6386
|
+
const type = record.type;
|
|
6387
|
+
if (typeof type !== "string")
|
|
6388
|
+
return;
|
|
6389
|
+
const seq = record.seq;
|
|
6390
|
+
if (typeof seq !== "number" || !Number.isInteger(seq) || seq < 0 || seq >= WORKFLOW_LEDGER_MAX_SEQ)
|
|
6391
|
+
return;
|
|
6392
|
+
const time = record.time;
|
|
6393
|
+
if (typeof time !== "number" || !Number.isFinite(time))
|
|
6394
|
+
return;
|
|
6395
|
+
const data = asRecord(record.data);
|
|
6396
|
+
if (data === undefined)
|
|
6397
|
+
return;
|
|
6398
|
+
const runId = data.runId;
|
|
6399
|
+
if (typeof runId !== "string" || runId === "" || runId.length > WORKFLOW_LEDGER_MAX_ID_LENGTH)
|
|
6400
|
+
return;
|
|
6401
|
+
if (type === TOOL_WORKFLOW_RUN_START) {
|
|
6402
|
+
const name = typeof data.name === "string" ? normalizeWorkflowName(data.name) : data.name;
|
|
6403
|
+
if (typeof name !== "string" || name === "")
|
|
6404
|
+
return;
|
|
6405
|
+
const agent = sessionIdOf2(session);
|
|
6406
|
+
return {
|
|
6407
|
+
seq,
|
|
6408
|
+
runName: name,
|
|
6409
|
+
event: {
|
|
6410
|
+
v: 1,
|
|
6411
|
+
ts: time,
|
|
6412
|
+
kind: "workflow-run",
|
|
6413
|
+
runId,
|
|
6414
|
+
name: truncateLedgerField(name, WORKFLOW_LEDGER_MAX_NAME_LENGTH),
|
|
6415
|
+
...agent !== undefined ? { agent } : {}
|
|
6416
|
+
}
|
|
6417
|
+
};
|
|
6418
|
+
}
|
|
6419
|
+
if (type === TOOL_WORKFLOW_AGENT_START) {
|
|
6420
|
+
if (typeof data.seq !== "number" || !Number.isInteger(data.seq) || data.seq < 1 || data.seq >= WORKFLOW_LEDGER_MAX_SEQ) {
|
|
6421
|
+
return;
|
|
6422
|
+
}
|
|
6423
|
+
const label = typeof data.label === "string" ? normalizeWorkflowName(data.label) : data.label;
|
|
6424
|
+
if (typeof label !== "string" || label === "")
|
|
6425
|
+
return;
|
|
6426
|
+
if (typeof data.childId !== "string" || data.childId === "" || data.childId.length > WORKFLOW_LEDGER_MAX_ID_LENGTH)
|
|
6427
|
+
return;
|
|
6428
|
+
return {
|
|
6429
|
+
seq,
|
|
6430
|
+
childId: data.childId,
|
|
6431
|
+
event: {
|
|
6432
|
+
v: 1,
|
|
6433
|
+
ts: time,
|
|
6434
|
+
kind: "workflow-agent",
|
|
6435
|
+
runId,
|
|
6436
|
+
seq: data.seq,
|
|
6437
|
+
label: truncateLedgerField(label, WORKFLOW_LEDGER_MAX_LABEL_LENGTH),
|
|
6438
|
+
...typeof data.phase === "string" ? { phase: truncateLedgerField(normalizeWorkflowName(data.phase), WORKFLOW_LEDGER_MAX_LABEL_LENGTH) } : {},
|
|
6439
|
+
childId: data.childId
|
|
6440
|
+
}
|
|
6441
|
+
};
|
|
6442
|
+
}
|
|
6443
|
+
if (type === TOOL_WORKFLOW_AGENT_END) {
|
|
6444
|
+
return;
|
|
6445
|
+
}
|
|
6446
|
+
if (type === TOOL_WORKFLOW_RUN_END) {
|
|
6447
|
+
const stopReason = data.stopReason;
|
|
6448
|
+
if (stopReason !== "completed" && stopReason !== "cancelled" && stopReason !== "error")
|
|
6449
|
+
return;
|
|
6450
|
+
return {
|
|
6451
|
+
seq,
|
|
6452
|
+
event: { v: 1, ts: time, kind: "workflow-run-end", runId, stopReason }
|
|
6453
|
+
};
|
|
6454
|
+
}
|
|
6455
|
+
return;
|
|
6456
|
+
}
|
|
6457
|
+
function depthAdvisory(sessions, row, warned) {
|
|
6458
|
+
if (row.event.kind !== "workflow-agent" || row.childId === undefined)
|
|
6459
|
+
return;
|
|
6460
|
+
if (warned.has(row.event.runId))
|
|
6461
|
+
return;
|
|
6462
|
+
const child = sessions.get(row.childId);
|
|
6463
|
+
const delegationDepth = child?.header?.delegationDepth;
|
|
6464
|
+
if (typeof delegationDepth !== "number" || !Number.isFinite(delegationDepth) || delegationDepth < DEPTH_ADVISORY_THRESHOLD) {
|
|
6465
|
+
return;
|
|
6466
|
+
}
|
|
6467
|
+
warned.add(row.event.runId);
|
|
6468
|
+
log3("warn", `workflow run ${row.event.runId} member '${row.event.label}' (child ${row.childId}) is at delegation depth ${delegationDepth} (>= ${DEPTH_ADVISORY_THRESHOLD}) — advisory only, the run proceeds`);
|
|
6469
|
+
}
|
|
6470
|
+
function registerWorkflowLedger(ctx, resolver, workflowAskCache) {
|
|
6471
|
+
const sessions = ctx.get("sessions");
|
|
6472
|
+
if (sessions === undefined) {
|
|
6473
|
+
log3("debug", "sessions service absent — workflow-ledger consumer disabled (composition without dsh-session)");
|
|
6474
|
+
return;
|
|
6475
|
+
}
|
|
6476
|
+
const depthWarned = new Set;
|
|
6477
|
+
const consume = (session, envelope) => {
|
|
6478
|
+
const sid = sessionIdOf2(session);
|
|
6479
|
+
if (sid === undefined)
|
|
6480
|
+
return;
|
|
6481
|
+
const row = rowOf(session, envelope);
|
|
6482
|
+
if (row === undefined)
|
|
6483
|
+
return;
|
|
6484
|
+
const cwd = session?.header?.cwd;
|
|
6485
|
+
const harnessDir = resolver.forWorkspace(typeof cwd === "string" && cwd.trim() !== "" ? cwd : undefined);
|
|
6486
|
+
if (harnessDir === null)
|
|
6487
|
+
return;
|
|
6488
|
+
const watermark = loadWatermark(harnessDir);
|
|
6489
|
+
let next = watermark.get(sid) ?? 0;
|
|
6490
|
+
const events = session.events;
|
|
6491
|
+
if (Array.isArray(events) && events.length < next)
|
|
6492
|
+
next = 0;
|
|
6493
|
+
if (row.seq < next)
|
|
6494
|
+
return;
|
|
6495
|
+
if (recordWorkflowEvent({ harnessDir, event: row.event })) {
|
|
6496
|
+
advanceWatermark(harnessDir, sid, row.seq + 1, (candidate) => sessions.get(candidate) === undefined);
|
|
6497
|
+
if (workflowAskCache !== undefined && row.event.kind === "workflow-run") {
|
|
6498
|
+
try {
|
|
6499
|
+
const runName = row.runName ?? row.event.name;
|
|
6500
|
+
if (workflowAskCache.wasAsked(runName)) {
|
|
6501
|
+
workflowAskCache.record(runName, "allow");
|
|
6502
|
+
}
|
|
6503
|
+
} catch (error) {
|
|
6504
|
+
log3("warn", `workflow P-c allow observation degraded (contained — the ledger row stays): ${errorMessage3(error)}`);
|
|
6505
|
+
}
|
|
6506
|
+
}
|
|
6507
|
+
}
|
|
6508
|
+
if (row.event.kind === "workflow-agent") {
|
|
6509
|
+
try {
|
|
6510
|
+
depthAdvisory(sessions, row, depthWarned);
|
|
6511
|
+
} catch (error) {
|
|
6512
|
+
log3("warn", `workflow depth advisory degraded (contained — the run proceeds): ${errorMessage3(error)}`);
|
|
6513
|
+
}
|
|
6514
|
+
}
|
|
6515
|
+
};
|
|
6516
|
+
const scanSession = (session) => {
|
|
6517
|
+
const sid = sessionIdOf2(session);
|
|
6518
|
+
if (sid === undefined)
|
|
6519
|
+
return;
|
|
6520
|
+
const events = session.events;
|
|
6521
|
+
if (!Array.isArray(events))
|
|
6522
|
+
return;
|
|
6523
|
+
for (const envelope of events)
|
|
6524
|
+
consume(session, envelope);
|
|
6525
|
+
};
|
|
6526
|
+
ctx.events.on("session/created", (session) => {
|
|
6527
|
+
try {
|
|
6528
|
+
scanSession(session);
|
|
6529
|
+
} catch (error) {
|
|
6530
|
+
log3("warn", `workflow-ledger session-created backfill failed (contained — the session publication proceeds): ${errorMessage3(error)}`);
|
|
6531
|
+
}
|
|
6532
|
+
});
|
|
6533
|
+
let sessionsSnapshot;
|
|
6534
|
+
try {
|
|
6535
|
+
sessionsSnapshot = sessions.list();
|
|
6536
|
+
} catch (error) {
|
|
6537
|
+
log3("warn", `workflow-ledger cold scan could not list sessions (contained — the live firehose and session-created backfill stay active): ${errorMessage3(error)}`);
|
|
6538
|
+
sessionsSnapshot = [];
|
|
6539
|
+
}
|
|
6540
|
+
for (const session of sessionsSnapshot) {
|
|
6541
|
+
const sid = sessionIdOf2(session);
|
|
6542
|
+
if (sid === undefined)
|
|
6543
|
+
continue;
|
|
6544
|
+
try {
|
|
6545
|
+
scanSession(session);
|
|
6546
|
+
} catch (error) {
|
|
6547
|
+
log3("warn", `workflow-ledger cold scan failed for session ${sid} (contained — other sessions unaffected): ${errorMessage3(error)}`);
|
|
6548
|
+
}
|
|
6549
|
+
}
|
|
6550
|
+
ctx.events.on("session/event", (session, envelope) => {
|
|
6551
|
+
try {
|
|
6552
|
+
consume(session, envelope);
|
|
6553
|
+
} catch (error) {
|
|
6554
|
+
log3("warn", `workflow-ledger live consume failed (contained — the workflow run proceeds): ${errorMessage3(error)}`);
|
|
6555
|
+
}
|
|
6556
|
+
});
|
|
6557
|
+
}
|
|
6558
|
+
|
|
6559
|
+
// src/gates/goal-bridge.ts
|
|
6560
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14, readdirSync as readdirSync8 } from "node:fs";
|
|
6561
|
+
import { join as join18 } from "node:path";
|
|
6562
|
+
var GOAL_BRIDGE_LOGGER = "mstar/goal-bridge";
|
|
6563
|
+
var DEFAULT_MAX_GOAL_ROUNDS = 256;
|
|
6564
|
+
var FLOW_SEQUENCE = "iteration-start → per-plan cycles → iteration-close → PR delivery → merge-ready";
|
|
6565
|
+
var GOAL_CHANGE_EVENT_TYPE = "goal/change";
|
|
6566
|
+
var GOAL_CHANGE_VERSION = 1;
|
|
6567
|
+
var GOAL_ADVISORY_OBJECTIVE_CAP = 512;
|
|
6568
|
+
var GOAL_ADVISORY_MESSAGE_CAP = 512;
|
|
6569
|
+
var GOAL_ADVISORY_CODE_CAP = 128;
|
|
6570
|
+
var goalBridgeLogSink = () => {};
|
|
6571
|
+
function setGoalBridgeLogger(sink) {
|
|
6572
|
+
const prior = goalBridgeLogSink;
|
|
6573
|
+
goalBridgeLogSink = sink;
|
|
6574
|
+
return prior;
|
|
6575
|
+
}
|
|
6576
|
+
function log4(level, message) {
|
|
6577
|
+
goalBridgeLogSink(level, message);
|
|
6578
|
+
}
|
|
6579
|
+
function errorMessage4(error) {
|
|
6580
|
+
return error instanceof Error ? error.message : String(error);
|
|
6581
|
+
}
|
|
6582
|
+
function isRootLikeAgent(agent) {
|
|
6583
|
+
const header = agent?.session?.header;
|
|
6584
|
+
return header?.parentSession === undefined;
|
|
6585
|
+
}
|
|
6586
|
+
function iterationGoalObjective(iterationId) {
|
|
6587
|
+
return `Run iteration ${iterationId} through the complete flow to its exit: ${FLOW_SEQUENCE}. ` + "Exit: the iteration's delivery PR is merged to the target branch and the merge-ready loop closes; the harness status.json stays the source of truth.";
|
|
6588
|
+
}
|
|
6589
|
+
function steeringCompass(harnessDir) {
|
|
6590
|
+
const iterationsDir = resolveIterationDir(harnessDir);
|
|
6591
|
+
if (!existsSync16(iterationsDir))
|
|
6592
|
+
return;
|
|
6593
|
+
let entries;
|
|
6594
|
+
try {
|
|
6595
|
+
entries = readdirSync8(iterationsDir, { withFileTypes: true });
|
|
6596
|
+
} catch {
|
|
6597
|
+
return;
|
|
6598
|
+
}
|
|
6599
|
+
for (const entry of entries) {
|
|
6600
|
+
if (!entry.isDirectory())
|
|
6601
|
+
continue;
|
|
6602
|
+
const compassPath = join18(iterationsDir, entry.name, "delivery-compass.md");
|
|
6603
|
+
if (!existsSync16(compassPath))
|
|
6604
|
+
continue;
|
|
6605
|
+
let content;
|
|
6606
|
+
try {
|
|
6607
|
+
content = readFileSync14(compassPath, "utf8");
|
|
6608
|
+
} catch {
|
|
6609
|
+
continue;
|
|
6610
|
+
}
|
|
6611
|
+
const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
6612
|
+
if (frontmatter === null || !/^status[ \t]*:[ \t]*(?:active|locked)[ \t]*$/m.test(frontmatter[1]))
|
|
6613
|
+
continue;
|
|
6614
|
+
return { iterationId: entry.name };
|
|
6615
|
+
}
|
|
6616
|
+
return;
|
|
6617
|
+
}
|
|
6618
|
+
var GOAL_STALE_REVISION = "GOAL_STALE_REVISION";
|
|
6619
|
+
function goalErrorCode(error) {
|
|
6620
|
+
if (typeof error !== "object" || error === null)
|
|
6621
|
+
return;
|
|
6622
|
+
if (!("code" in error))
|
|
6623
|
+
return;
|
|
6624
|
+
const code = error.code;
|
|
6625
|
+
return typeof code === "string" ? code : undefined;
|
|
6626
|
+
}
|
|
6627
|
+
function resolveGoalRounds(configValue) {
|
|
6628
|
+
if (configValue === undefined)
|
|
6629
|
+
return DEFAULT_MAX_GOAL_ROUNDS;
|
|
6630
|
+
if (!Number.isSafeInteger(configValue) || configValue < 1) {
|
|
6631
|
+
log4("warn", `invalid maxGoalRounds config ${String(configValue)} (must be a positive safe integer) — falling back to the default ${DEFAULT_MAX_GOAL_ROUNDS}`);
|
|
6632
|
+
return DEFAULT_MAX_GOAL_ROUNDS;
|
|
6633
|
+
}
|
|
6634
|
+
return configValue;
|
|
6635
|
+
}
|
|
6636
|
+
function replaceDriftedGoal(goals, agent, current, objective, maxGoalRounds) {
|
|
6637
|
+
if (current.phase === "complete") {
|
|
6638
|
+
goals.create(agent, { objective, maxGoalRounds });
|
|
6639
|
+
return;
|
|
6640
|
+
}
|
|
6641
|
+
goals.complete(agent, { id: current.id, revision: current.revision });
|
|
6642
|
+
goals.create(agent, { objective, maxGoalRounds });
|
|
6643
|
+
}
|
|
6644
|
+
function mirrorIterationGoal(agent, input) {
|
|
6645
|
+
const { resolver, goals, maxGoalRounds } = input;
|
|
6646
|
+
if (goals === undefined)
|
|
6647
|
+
return false;
|
|
6648
|
+
if (!isRootLikeAgent(agent))
|
|
6649
|
+
return false;
|
|
6650
|
+
const harnessDir = resolver.forAgent(agent);
|
|
6651
|
+
if (harnessDir === null)
|
|
6652
|
+
return false;
|
|
6653
|
+
const compass = steeringCompass(harnessDir);
|
|
6654
|
+
if (compass === undefined)
|
|
6655
|
+
return false;
|
|
6656
|
+
const objective = iterationGoalObjective(compass.iterationId);
|
|
6657
|
+
try {
|
|
6658
|
+
const current = goals.get(agent);
|
|
6659
|
+
if (current === undefined) {
|
|
6660
|
+
goals.create(agent, { objective, maxGoalRounds });
|
|
6661
|
+
return true;
|
|
6662
|
+
}
|
|
6663
|
+
if (current.objective === objective)
|
|
6664
|
+
return true;
|
|
6665
|
+
try {
|
|
6666
|
+
replaceDriftedGoal(goals, agent, current, objective, maxGoalRounds);
|
|
6667
|
+
return true;
|
|
6668
|
+
} catch (error) {
|
|
6669
|
+
if (goalErrorCode(error) !== GOAL_STALE_REVISION)
|
|
6670
|
+
throw error;
|
|
6671
|
+
const fresh = goals.get(agent);
|
|
6672
|
+
if (fresh === undefined) {
|
|
6673
|
+
goals.create(agent, { objective, maxGoalRounds });
|
|
6674
|
+
return true;
|
|
6675
|
+
}
|
|
6676
|
+
if (fresh.objective === objective)
|
|
6677
|
+
return true;
|
|
6678
|
+
replaceDriftedGoal(goals, agent, fresh, objective, maxGoalRounds);
|
|
6679
|
+
return true;
|
|
6680
|
+
}
|
|
6681
|
+
} catch (error) {
|
|
6682
|
+
const code = goalErrorCode(error);
|
|
6683
|
+
log4("warn", `goal mirror failed (contained — the session/decision point proceeds)${code !== undefined ? ` [${code}]` : ""}: ${errorMessage4(error)}`);
|
|
6684
|
+
return false;
|
|
6685
|
+
}
|
|
6686
|
+
}
|
|
6687
|
+
function blockedAdvisoryOf(envelope) {
|
|
6688
|
+
const record = asRecord(envelope);
|
|
6689
|
+
if (record === undefined || record.type !== GOAL_CHANGE_EVENT_TYPE)
|
|
6690
|
+
return;
|
|
6691
|
+
const data = asRecord(record.data);
|
|
6692
|
+
if (data === undefined)
|
|
6693
|
+
return;
|
|
6694
|
+
if (data.version !== GOAL_CHANGE_VERSION)
|
|
6695
|
+
return;
|
|
6696
|
+
const goal = asRecord(data.goal);
|
|
6697
|
+
if (goal === undefined)
|
|
6698
|
+
return;
|
|
6699
|
+
if (data.operation !== "block" && goal.phase !== "blocked")
|
|
6700
|
+
return;
|
|
6701
|
+
const blockedReason = asRecord(goal.blockedReason);
|
|
6702
|
+
if (blockedReason === undefined)
|
|
6703
|
+
return;
|
|
6704
|
+
const code = blockedReason.code;
|
|
6705
|
+
const message = blockedReason.message;
|
|
6706
|
+
const objective = goal.objective;
|
|
6707
|
+
if (typeof code !== "string" || code === "" || typeof message !== "string" || message === "" || typeof objective !== "string" || objective === "") {
|
|
6708
|
+
return;
|
|
6709
|
+
}
|
|
6710
|
+
return { code, message, objective };
|
|
6711
|
+
}
|
|
6712
|
+
function warnBlockedGoal(harnessDir, advisory) {
|
|
6713
|
+
const code = truncateLedgerField(advisory.code, GOAL_ADVISORY_CODE_CAP);
|
|
6714
|
+
const reason = truncateLedgerField(normalizeWorkflowName(advisory.message), GOAL_ADVISORY_MESSAGE_CAP);
|
|
6715
|
+
const objective = truncateLedgerField(normalizeWorkflowName(advisory.objective), GOAL_ADVISORY_OBJECTIVE_CAP);
|
|
6716
|
+
log4("warn", `goal blocked [${code}] — ${reason}; objective: ${objective}; residuals: see ${harnessDir}/${STATUS_FILE} (residual_findings) — advisory only, zero harness writes`);
|
|
6717
|
+
}
|
|
6718
|
+
function rootAgentOf(agent, agents) {
|
|
6719
|
+
let current = agent;
|
|
6720
|
+
const seen = new Set;
|
|
6721
|
+
for (;; ) {
|
|
6722
|
+
const header = current?.session?.header;
|
|
6723
|
+
if (header === undefined)
|
|
6724
|
+
return;
|
|
6725
|
+
if (header.parentSession === undefined)
|
|
6726
|
+
return current;
|
|
6727
|
+
if (typeof header.parentSession !== "string")
|
|
6728
|
+
return;
|
|
6729
|
+
if (seen.has(header.parentSession))
|
|
6730
|
+
return;
|
|
6731
|
+
seen.add(header.parentSession);
|
|
6732
|
+
const parent = agents.get(header.parentSession);
|
|
6733
|
+
if (parent === undefined)
|
|
6734
|
+
return;
|
|
6735
|
+
current = parent;
|
|
6736
|
+
}
|
|
6737
|
+
}
|
|
6738
|
+
function registerGoalBridge(ctx, resolver, config) {
|
|
6739
|
+
const goals = ctx.get("goals");
|
|
6740
|
+
if (goals === undefined) {
|
|
6741
|
+
log4("debug", "goals service absent — goal bridge disabled (composition without @deepseek-ai/dsh-goal)");
|
|
6742
|
+
}
|
|
6743
|
+
const maxGoalRounds = resolveGoalRounds(config.maxGoalRounds);
|
|
6744
|
+
const mirror = (agent) => {
|
|
6745
|
+
try {
|
|
6746
|
+
mirrorIterationGoal(agent, { resolver, goals, maxGoalRounds });
|
|
6747
|
+
} catch (error) {
|
|
6748
|
+
log4("warn", `goal bridge mirror failed (contained — the session/decision point proceeds): ${errorMessage4(error)}`);
|
|
6749
|
+
}
|
|
6750
|
+
};
|
|
6751
|
+
ctx.events.on("agent/session-start", (payload) => {
|
|
6752
|
+
if (payload.agent !== undefined)
|
|
6753
|
+
mirror(payload.agent);
|
|
6754
|
+
});
|
|
6755
|
+
ctx.events.on("subagent/start", (info) => {
|
|
6756
|
+
const agents = ctx.get("agents");
|
|
6757
|
+
if (agents === undefined || typeof info.id !== "string")
|
|
6758
|
+
return;
|
|
6759
|
+
const child = agents.get(info.id);
|
|
6760
|
+
if (child === undefined)
|
|
6761
|
+
return;
|
|
6762
|
+
const root = rootAgentOf(child, agents);
|
|
6763
|
+
if (root !== undefined)
|
|
6764
|
+
mirror(root);
|
|
6765
|
+
});
|
|
6766
|
+
ctx.events.on("session/event", (session, envelope) => {
|
|
6767
|
+
try {
|
|
6768
|
+
const advisory = blockedAdvisoryOf(envelope);
|
|
6769
|
+
if (advisory === undefined)
|
|
6770
|
+
return;
|
|
6771
|
+
const cwd = session?.header?.cwd;
|
|
6772
|
+
const harnessDir = resolver.forWorkspace(typeof cwd === "string" && cwd.trim() !== "" ? cwd : undefined);
|
|
6773
|
+
if (harnessDir === null)
|
|
6774
|
+
return;
|
|
6775
|
+
warnBlockedGoal(harnessDir, advisory);
|
|
6776
|
+
} catch (error) {
|
|
6777
|
+
log4("warn", `goal blocked advisory degraded (contained — the session proceeds): ${errorMessage4(error)}`);
|
|
6778
|
+
}
|
|
6779
|
+
});
|
|
6780
|
+
}
|
|
6781
|
+
|
|
6782
|
+
// src/gates/plan-mode-bridge.ts
|
|
6783
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
6784
|
+
import { join as join19 } from "node:path";
|
|
6785
|
+
var PLAN_MODE_BRIDGE_LOGGER = "mstar/plan-mode-bridge";
|
|
6786
|
+
var PLAN_STATUS_TODO = "Todo";
|
|
6787
|
+
var planModeBridgeLogSink = () => {};
|
|
6788
|
+
function setPlanModeBridgeLogger(sink) {
|
|
6789
|
+
const prior = planModeBridgeLogSink;
|
|
6790
|
+
planModeBridgeLogSink = sink;
|
|
6791
|
+
return prior;
|
|
6792
|
+
}
|
|
6793
|
+
function log5(level, message) {
|
|
6794
|
+
planModeBridgeLogSink(level, message);
|
|
6795
|
+
}
|
|
6796
|
+
function errorMessage5(error) {
|
|
6797
|
+
return error instanceof Error ? error.message : String(error);
|
|
6798
|
+
}
|
|
6799
|
+
function hasPrepareWindow(harnessDir) {
|
|
6800
|
+
const statusPath = join19(harnessDir, STATUS_FILE);
|
|
6801
|
+
if (!existsSync17(statusPath))
|
|
6802
|
+
return false;
|
|
6803
|
+
let doc;
|
|
6804
|
+
try {
|
|
6805
|
+
doc = readJson(statusPath);
|
|
6806
|
+
} catch {
|
|
6807
|
+
return false;
|
|
6808
|
+
}
|
|
6809
|
+
const plans = doc.plans;
|
|
6810
|
+
if (!Array.isArray(plans))
|
|
6811
|
+
return false;
|
|
6812
|
+
return plans.some((row) => asRecord(row)?.status === PLAN_STATUS_TODO);
|
|
6813
|
+
}
|
|
6814
|
+
function planModeTarget(harnessDir) {
|
|
6815
|
+
if (steeringCompass(harnessDir) === undefined)
|
|
6816
|
+
return false;
|
|
6817
|
+
return hasPrepareWindow(harnessDir);
|
|
6818
|
+
}
|
|
6819
|
+
function syncPlanMode(agent, input) {
|
|
6820
|
+
const { resolver, planMode } = input;
|
|
6821
|
+
if (planMode === undefined)
|
|
6822
|
+
return false;
|
|
6823
|
+
if (!isRootLikeAgent(agent))
|
|
6824
|
+
return false;
|
|
6825
|
+
const harnessDir = resolver.forAgent(agent);
|
|
6826
|
+
if (harnessDir === null)
|
|
6827
|
+
return false;
|
|
6828
|
+
try {
|
|
6829
|
+
planMode.set(agent, planModeTarget(harnessDir));
|
|
6830
|
+
return true;
|
|
6831
|
+
} catch (error) {
|
|
6832
|
+
log5("warn", `planMode sync failed (contained — the session/decision point proceeds): ${errorMessage5(error)}`);
|
|
6833
|
+
return false;
|
|
6834
|
+
}
|
|
6835
|
+
}
|
|
6836
|
+
function registerPlanModeBridge(ctx, resolver) {
|
|
6837
|
+
const planMode = ctx.get("planMode");
|
|
6838
|
+
if (planMode === undefined) {
|
|
6839
|
+
log5("debug", "planMode service absent — plan-mode bridge disabled (composition without @deepseek-ai/dsh-plan-mode)");
|
|
6840
|
+
}
|
|
6841
|
+
const sync = (agent) => {
|
|
6842
|
+
try {
|
|
6843
|
+
syncPlanMode(agent, { resolver, planMode });
|
|
6844
|
+
} catch (error) {
|
|
6845
|
+
log5("warn", `planMode bridge sync failed (contained — the session/decision point proceeds): ${errorMessage5(error)}`);
|
|
6846
|
+
}
|
|
6847
|
+
};
|
|
6848
|
+
ctx.events.on("agent/session-start", (payload) => {
|
|
6849
|
+
if (payload.agent !== undefined)
|
|
6850
|
+
sync(payload.agent);
|
|
6851
|
+
});
|
|
6852
|
+
ctx.events.on("subagent/start", (info) => {
|
|
6853
|
+
const agents = ctx.get("agents");
|
|
6854
|
+
if (agents === undefined || typeof info.id !== "string")
|
|
6855
|
+
return;
|
|
6856
|
+
const child = agents.get(info.id);
|
|
6857
|
+
if (child === undefined)
|
|
6858
|
+
return;
|
|
6859
|
+
const root = rootAgentOf(child, agents);
|
|
6860
|
+
if (root !== undefined)
|
|
6861
|
+
sync(root);
|
|
6862
|
+
});
|
|
6863
|
+
}
|
|
6864
|
+
|
|
6865
|
+
// src/gates/fallbacks-seeds.ts
|
|
6866
|
+
var SEEDS_LOGGER = "mstar/fallbacks-seeds";
|
|
6867
|
+
var EMPTY_OUTCOME = { applied: [], skipped: [], conflicts: [] };
|
|
6868
|
+
function errorMessage6(error) {
|
|
6869
|
+
return error instanceof Error ? error.message : String(error);
|
|
6870
|
+
}
|
|
6871
|
+
function mstarLoadLine(roleId) {
|
|
6872
|
+
return `Load mstar-roles (references/${roleId}.md) and the role's Required Skill Dependencies before acting.`;
|
|
6873
|
+
}
|
|
6874
|
+
async function declareMstarSeeds(service, options) {
|
|
6875
|
+
const { agentsDir, log: log6 } = options;
|
|
6876
|
+
let readback;
|
|
6877
|
+
try {
|
|
6878
|
+
readback = service.getEffectiveRoles();
|
|
6879
|
+
} catch (error) {
|
|
6880
|
+
log6("warn", `fallbacks seed readback failed — mstar seeds not declared: ${errorMessage6(error)}`);
|
|
6881
|
+
return { declared: [], skipped: [], preserved: [], outcome: EMPTY_OUTCOME };
|
|
6882
|
+
}
|
|
6883
|
+
const mstarIds = agentsDir === undefined ? [] : subagentRoleIds(agentsDir);
|
|
6884
|
+
const mstarSet = new Set(mstarIds);
|
|
6885
|
+
const preservedCandidates = [];
|
|
6886
|
+
for (const row of readback.roles) {
|
|
6887
|
+
if (!row.seeded)
|
|
6888
|
+
continue;
|
|
6889
|
+
const id = row.id.trim();
|
|
6890
|
+
if (id === "" || mstarSet.has(id))
|
|
6891
|
+
continue;
|
|
6892
|
+
if (row.seedPersona === undefined || row.seedPersona.trim() === "") {
|
|
6893
|
+
log6("debug", `seeded row '${id}' carries no seedPersona (upstream contract: present iff seeded) — not preserved`);
|
|
6894
|
+
continue;
|
|
6895
|
+
}
|
|
6896
|
+
preservedCandidates.push({ id, persona: row.seedPersona });
|
|
6897
|
+
}
|
|
6898
|
+
const declared = [];
|
|
6899
|
+
const skipped = [];
|
|
6900
|
+
const forwardWarn = (message) => log6("warn", message);
|
|
6901
|
+
for (const roleId of mstarIds) {
|
|
6902
|
+
const persona = personaFor(roleId, { agentsDir }, forwardWarn);
|
|
6903
|
+
if (persona === undefined) {
|
|
6904
|
+
skipped.push({ id: roleId, reason: "no-persona" });
|
|
6905
|
+
continue;
|
|
6906
|
+
}
|
|
6907
|
+
if (PERSONA_INTERPOLATION_HAZARD.test(persona.text)) {
|
|
6908
|
+
log6("warn", `seed persona for mstar role '${roleId}' contains a "{{" paired with a later "}}" (dsh system-prompt strict interpolation renders persona text and throws on unknown or malformed references) — skipped`);
|
|
6909
|
+
skipped.push({ id: roleId, reason: "interpolation" });
|
|
6910
|
+
continue;
|
|
6911
|
+
}
|
|
6912
|
+
declared.push({ id: roleId, persona: `${persona.text}
|
|
6913
|
+
|
|
6914
|
+
${mstarLoadLine(roleId)}` });
|
|
6915
|
+
}
|
|
6916
|
+
const preserved = [];
|
|
6917
|
+
for (const entry of preservedCandidates) {
|
|
6918
|
+
if (PERSONA_INTERPOLATION_HAZARD.test(entry.persona)) {
|
|
6919
|
+
log6("warn", `preserved seed persona for '${entry.id}' contains a "{{" paired with a later "}}" (dsh system-prompt strict interpolation renders persona text and throws on unknown or malformed references) — skipped`);
|
|
6920
|
+
skipped.push({ id: entry.id, reason: "interpolation" });
|
|
6921
|
+
continue;
|
|
6922
|
+
}
|
|
6923
|
+
declared.push({ id: entry.id, persona: entry.persona });
|
|
6924
|
+
preserved.push(entry);
|
|
6925
|
+
}
|
|
6926
|
+
if (declared.length === 0) {
|
|
6927
|
+
log6("debug", "no mstar or preserved seeds to declare — empty batch skipped (registry untouched)");
|
|
6928
|
+
return { declared, skipped, preserved, outcome: EMPTY_OUTCOME };
|
|
6929
|
+
}
|
|
6930
|
+
const outcome = await service.declareSeeds(declared);
|
|
6931
|
+
log6("debug", `mstar seeds declared: ${declared.length} ids (${mstarIds.length} mstar + ${preserved.length} preserved); ${skipped.length} skipped locally`);
|
|
6932
|
+
return { declared, skipped, preserved, outcome };
|
|
6933
|
+
}
|
|
6934
|
+
|
|
6935
|
+
// src/gates/fallbacks-advisory.ts
|
|
6936
|
+
var ADVISORY_LOGGER = "mstar/fallbacks-advisory";
|
|
6937
|
+
var advisoryLogSink = () => {};
|
|
6938
|
+
function setAdvisoryLogger(sink) {
|
|
6939
|
+
const prior = advisoryLogSink;
|
|
6940
|
+
advisoryLogSink = sink;
|
|
6941
|
+
return prior;
|
|
6942
|
+
}
|
|
6943
|
+
var ADVISORY_ID_LIST_CAP = 20;
|
|
6944
|
+
function capIdList(items) {
|
|
6945
|
+
if (items.length <= ADVISORY_ID_LIST_CAP)
|
|
6946
|
+
return items.join(", ");
|
|
6947
|
+
return `${items.slice(0, ADVISORY_ID_LIST_CAP).join(", ")}… and ${items.length - ADVISORY_ID_LIST_CAP} more`;
|
|
6948
|
+
}
|
|
6949
|
+
async function runFallbacksAdvisory(ctx, agentsDir) {
|
|
6950
|
+
try {
|
|
6951
|
+
if (!fallbacksMounted(ctx))
|
|
6952
|
+
return false;
|
|
6953
|
+
const config = readRowConfig(ctx);
|
|
6954
|
+
if (config === undefined) {
|
|
6955
|
+
log6("debug", "fallbacks row config unreadable (absent or non-object) — adoption advisory skipped");
|
|
6956
|
+
return true;
|
|
6957
|
+
}
|
|
6958
|
+
const service = fallbacksService(ctx);
|
|
6959
|
+
if (service !== undefined) {
|
|
6960
|
+
return await runSeedsAdvisory(service, config, agentsDir);
|
|
6961
|
+
}
|
|
6962
|
+
return runStructuralAdvisory(config, agentsDir);
|
|
6963
|
+
} catch (error) {
|
|
6964
|
+
log6("warn", `fallbacks adoption advisory aborted (degraded — warn-only, deployment config untouched): ${errorMessage7(error)}`);
|
|
6965
|
+
return true;
|
|
6966
|
+
}
|
|
6967
|
+
}
|
|
6968
|
+
async function runSeedsAdvisory(service, config, agentsDir) {
|
|
6969
|
+
const legacy = service.detectLegacyKeys(config);
|
|
6970
|
+
if (legacy.length > 0) {
|
|
6971
|
+
log6("warn", `fallbacks config carries legacy keys (detectLegacyKeys): ${capIdList(legacy)} — migrate to the current shape (role entities use 'persona'; 'chains', 'roles.default', 'label' and 'description' are removed)`);
|
|
6972
|
+
}
|
|
6973
|
+
if (agentsDir === undefined) {
|
|
6974
|
+
log6("debug", "harness-agents mirror absent — fallbacks adoption taxonomy check skipped (config-only advisory)");
|
|
6975
|
+
return true;
|
|
6976
|
+
}
|
|
6977
|
+
const mstarIds = subagentRoleIds(agentsDir);
|
|
6978
|
+
if (mstarIds.length === 0) {
|
|
6979
|
+
log6("debug", "harness-agents mirror has no subagent-mode shells — fallbacks adoption taxonomy check skipped");
|
|
6980
|
+
return true;
|
|
6981
|
+
}
|
|
6982
|
+
const seedsLog = (_level, message) => log6("debug", message);
|
|
6983
|
+
const view = await declareMstarSeeds(service, { agentsDir, log: seedsLog });
|
|
6984
|
+
let readback;
|
|
6985
|
+
try {
|
|
6986
|
+
readback = service.getEffectiveRoles();
|
|
6987
|
+
} catch (error) {
|
|
6988
|
+
log6("warn", `fallbacks effective-state readback failed — adoption state unavailable: ${errorMessage7(error)}`);
|
|
6989
|
+
return true;
|
|
6990
|
+
}
|
|
6991
|
+
reportEffectiveState(mstarIds, readback);
|
|
6992
|
+
reportDeclareOutcome(view);
|
|
6993
|
+
return true;
|
|
6994
|
+
}
|
|
6995
|
+
function runStructuralAdvisory(config, agentsDir) {
|
|
6996
|
+
if (agentsDir === undefined) {
|
|
6997
|
+
log6("debug", "harness-agents mirror absent — fallbacks adoption taxonomy check skipped (config-only advisory)");
|
|
6998
|
+
return true;
|
|
6999
|
+
}
|
|
7000
|
+
const mstarIds = subagentRoleIds(agentsDir);
|
|
7001
|
+
if (mstarIds.length === 0) {
|
|
7002
|
+
log6("debug", "harness-agents mirror has no subagent-mode shells — fallbacks adoption taxonomy check skipped");
|
|
7003
|
+
return true;
|
|
7004
|
+
}
|
|
7005
|
+
const list = readRolesList(config);
|
|
7006
|
+
if (list === undefined) {
|
|
7007
|
+
log6("debug", "fallbacks roles block not structurally readable (roles.list absent or non-array) — adoption advisory skipped");
|
|
7008
|
+
return true;
|
|
7009
|
+
}
|
|
7010
|
+
const declared = new Map;
|
|
7011
|
+
for (let index = 0;index < list.length; index++) {
|
|
7012
|
+
const entity = list[index];
|
|
7013
|
+
if (entity === null || typeof entity !== "object") {
|
|
7014
|
+
log6("debug", `fallbacks roles.list entry ${index} is not an object (${entity === null ? "null" : typeof entity}) — skipped`);
|
|
7015
|
+
continue;
|
|
7016
|
+
}
|
|
7017
|
+
const view = entity;
|
|
7018
|
+
if (typeof view.id !== "string" || view.id === "")
|
|
7019
|
+
continue;
|
|
7020
|
+
declared.set(view.id, view);
|
|
7021
|
+
}
|
|
7022
|
+
const missing = mstarIds.filter((id) => !declared.has(id));
|
|
7023
|
+
if (missing.length > 0) {
|
|
7024
|
+
log6("warn", `fallbacks roles.list is missing mstar roles: ${capIdList(missing)} — declare them under the mstar role id (or map via a taxonomy that uses those ids) so role-matched fallback chains resolve`);
|
|
7025
|
+
}
|
|
7026
|
+
const emptyPersona = [...declared.values()].filter((entity) => {
|
|
7027
|
+
const persona = entity.persona;
|
|
7028
|
+
return typeof persona !== "string" || persona.trim() === "";
|
|
7029
|
+
}).map((entity) => entity.id);
|
|
7030
|
+
if (emptyPersona.length > 0) {
|
|
7031
|
+
log6("warn", `fallbacks roles.list declares roles with an empty persona: ${capIdList(emptyPersona)} — declare a persona (or remove the role)`);
|
|
7032
|
+
}
|
|
7033
|
+
return true;
|
|
7034
|
+
}
|
|
7035
|
+
function reportEffectiveState(mstarIds, readback) {
|
|
7036
|
+
const byId = new Map;
|
|
7037
|
+
for (const row of readback.roles) {
|
|
7038
|
+
const key = row.id.trim();
|
|
7039
|
+
if (byId.has(key)) {
|
|
7040
|
+
log6("debug", `effective readback carries a duplicate id '${key}' after trimming — first row wins for the three-state report`);
|
|
7041
|
+
continue;
|
|
7042
|
+
}
|
|
7043
|
+
byId.set(key, row);
|
|
7044
|
+
}
|
|
7045
|
+
const missing = [];
|
|
7046
|
+
const overridden = [];
|
|
7047
|
+
const seeded = [];
|
|
7048
|
+
for (const id of mstarIds) {
|
|
7049
|
+
const row = byId.get(id);
|
|
7050
|
+
if (row === undefined) {
|
|
7051
|
+
missing.push(id);
|
|
7052
|
+
continue;
|
|
7053
|
+
}
|
|
7054
|
+
if (row.personaOverridden) {
|
|
7055
|
+
overridden.push(id);
|
|
7056
|
+
continue;
|
|
7057
|
+
}
|
|
7058
|
+
if (row.seeded)
|
|
7059
|
+
seeded.push(id);
|
|
7060
|
+
}
|
|
7061
|
+
if (missing.length > 0) {
|
|
7062
|
+
log6("warn", `fallbacks taxonomy is missing mstar roles: ${capIdList(missing)} — declare them under the mstar role id (or map via a taxonomy that uses those ids) so role-matched fallback chains resolve`);
|
|
7063
|
+
}
|
|
7064
|
+
if (seeded.length > 0) {
|
|
7065
|
+
log6("debug", `fallbacks taxonomy: mstar roles seeded at their defaults — ${capIdList(seeded)}`);
|
|
7066
|
+
}
|
|
7067
|
+
if (overridden.length > 0) {
|
|
7068
|
+
log6("warn", `fallbacks taxonomy overrides the seed persona for mstar roles: ${capIdList(overridden)} — the operator override is retained; revert to the seed default via the fallbacks settings card rollback button (fallbacks/revert-seed gateway)`);
|
|
7069
|
+
}
|
|
7070
|
+
const emptyPersona = readback.roles.filter((row) => {
|
|
7071
|
+
const persona = row.persona;
|
|
7072
|
+
if (typeof persona === "string" && persona.trim() !== "")
|
|
7073
|
+
return false;
|
|
7074
|
+
return !row.seeded || row.personaOverridden;
|
|
7075
|
+
}).map((row) => row.id);
|
|
7076
|
+
if (emptyPersona.length > 0) {
|
|
7077
|
+
log6("warn", `fallbacks taxonomy declares roles with an empty persona: ${capIdList(emptyPersona)} — declare a persona (or remove the role)`);
|
|
7078
|
+
}
|
|
7079
|
+
}
|
|
7080
|
+
function reportDeclareOutcome(view) {
|
|
7081
|
+
const { skipped, outcome } = view;
|
|
7082
|
+
const conflicts = outcome.conflicts;
|
|
7083
|
+
if (skipped.length === 0 && outcome.skipped.length === 0 && conflicts.length === 0)
|
|
7084
|
+
return;
|
|
7085
|
+
const parts = [];
|
|
7086
|
+
if (skipped.length > 0) {
|
|
7087
|
+
parts.push(`skipped locally: ${capIdList(skipped.map((s) => `${s.id} (${s.reason})`))}`);
|
|
7088
|
+
}
|
|
7089
|
+
if (outcome.skipped.length > 0) {
|
|
7090
|
+
parts.push(`skipped upstream: ${capIdList(outcome.skipped.map((s) => `${s.id} (${s.reason})`))}`);
|
|
7091
|
+
}
|
|
7092
|
+
if (conflicts.length > 0) {
|
|
7093
|
+
parts.push(`conflicts: ${capIdList(conflicts.map((c) => `${c.id} (${c.kind} — operator override retained)`))}`);
|
|
7094
|
+
}
|
|
7095
|
+
const revertNote = conflicts.length > 0 ? " — revert an override via the fallbacks settings card rollback button (fallbacks/revert-seed gateway)" : "";
|
|
7096
|
+
log6("warn", `fallbacks seed declaration: ${parts.join("; ")}${revertNote}`);
|
|
7097
|
+
}
|
|
7098
|
+
function errorMessage7(error) {
|
|
7099
|
+
return error instanceof Error ? error.message : String(error);
|
|
7100
|
+
}
|
|
7101
|
+
function readRowConfig(ctx) {
|
|
7102
|
+
const entry = fallbacksEntry(ctx);
|
|
7103
|
+
if (entry === undefined)
|
|
7104
|
+
return;
|
|
7105
|
+
const config = entry.options.config;
|
|
7106
|
+
if (config === undefined || config === null || typeof config !== "object" || Array.isArray(config))
|
|
7107
|
+
return;
|
|
7108
|
+
return config;
|
|
7109
|
+
}
|
|
7110
|
+
function readRolesList(config) {
|
|
7111
|
+
const roles = config.roles;
|
|
7112
|
+
if (roles === undefined || roles === null || typeof roles !== "object" || Array.isArray(roles))
|
|
7113
|
+
return;
|
|
7114
|
+
const list = roles.list;
|
|
7115
|
+
if (!Array.isArray(list))
|
|
7116
|
+
return;
|
|
7117
|
+
return list;
|
|
7118
|
+
}
|
|
7119
|
+
function log6(level, message) {
|
|
7120
|
+
try {
|
|
7121
|
+
advisoryLogSink(level, message);
|
|
7122
|
+
} catch {}
|
|
7123
|
+
}
|
|
7124
|
+
|
|
7125
|
+
// src/gates/system-prompt.ts
|
|
7126
|
+
var HARNESS_PROMPT_LOGGER = "mstar/harness-prompt";
|
|
7127
|
+
var HARNESS_RULES_SECTION_NAME = "mstar:harness-rules";
|
|
7128
|
+
var HARNESS_RULES_SECTION_ORDER = 2;
|
|
7129
|
+
var ENGINE_STATUS_CONTEXT_NAME = "mstar:engine-status";
|
|
7130
|
+
var ENGINE_STATUS_CONTEXT_ORDER = 100;
|
|
7131
|
+
var harnessPromptLogSink = () => {};
|
|
7132
|
+
function setHarnessPromptLogger(sink) {
|
|
7133
|
+
const prior = harnessPromptLogSink;
|
|
7134
|
+
harnessPromptLogSink = sink;
|
|
7135
|
+
return prior;
|
|
7136
|
+
}
|
|
7137
|
+
function resolveAssemblyHarnessDir(context, resolver, fallback) {
|
|
7138
|
+
const agent = context?.agent;
|
|
7139
|
+
if (agent === undefined || agent === null)
|
|
7140
|
+
return fallback;
|
|
7141
|
+
try {
|
|
7142
|
+
return resolver.forAgent(agent);
|
|
7143
|
+
} catch (error) {
|
|
7144
|
+
log7("debug", `mstar:harness-rules per-assembly harness-dir resolution failed (falling back to the boot value): ${errorMessage8(error)}`);
|
|
7145
|
+
return fallback;
|
|
7146
|
+
}
|
|
7147
|
+
}
|
|
7148
|
+
function registerHarnessPrompt(ctx, options) {
|
|
7149
|
+
const systemPrompt = ctx.get("systemPrompt");
|
|
7150
|
+
if (systemPrompt === undefined || typeof systemPrompt.section !== "function" || typeof systemPrompt.context !== "function") {
|
|
7151
|
+
log7("debug", "systemPrompt service absent — mstar:harness-rules injection skipped (composition without dsh-system-prompt)");
|
|
7152
|
+
return false;
|
|
7153
|
+
}
|
|
7154
|
+
let bootHarnessDir;
|
|
7155
|
+
try {
|
|
7156
|
+
bootHarnessDir = options.resolver.forWorkspace(undefined);
|
|
7157
|
+
} catch (error) {
|
|
7158
|
+
log7("warn", `mstar:harness-rules injection aborted (degraded — session boot unaffected): ${errorMessage8(error)}`);
|
|
7159
|
+
return false;
|
|
7160
|
+
}
|
|
7161
|
+
ctx.inject(["systemPrompt"], (systemPromptCtx) => {
|
|
7162
|
+
try {
|
|
7163
|
+
const view = systemPromptCtx.systemPrompt;
|
|
7164
|
+
const disposeSection = view.section({
|
|
7165
|
+
name: HARNESS_RULES_SECTION_NAME,
|
|
7166
|
+
order: HARNESS_RULES_SECTION_ORDER,
|
|
7167
|
+
text: (context) => {
|
|
7168
|
+
const harnessDir = resolveAssemblyHarnessDir(context, options.resolver, bootHarnessDir);
|
|
7169
|
+
return harnessRulesText(harnessDir, sectionEnforcement(harnessDir));
|
|
7170
|
+
}
|
|
7171
|
+
});
|
|
7172
|
+
const disposeContext = view.context({
|
|
7173
|
+
name: ENGINE_STATUS_CONTEXT_NAME,
|
|
7174
|
+
order: ENGINE_STATUS_CONTEXT_ORDER,
|
|
7175
|
+
text: engineStatusProvider(ctx, options.resolver, bootHarnessDir)
|
|
7176
|
+
});
|
|
7177
|
+
systemPromptCtx.effect(function* () {
|
|
7178
|
+
yield () => {
|
|
7179
|
+
try {
|
|
7180
|
+
disposeSection();
|
|
7181
|
+
} catch {}
|
|
7182
|
+
try {
|
|
7183
|
+
disposeContext();
|
|
7184
|
+
} catch {}
|
|
7185
|
+
};
|
|
7186
|
+
}, "mstar:harness-rules disposer collection");
|
|
7187
|
+
} catch (error) {
|
|
7188
|
+
log7("warn", `mstar:harness-rules registration failed (contained — session boot unaffected): ${errorMessage8(error)}`);
|
|
7189
|
+
}
|
|
7190
|
+
});
|
|
7191
|
+
return true;
|
|
7192
|
+
}
|
|
7193
|
+
function sectionEnforcement(harnessDir) {
|
|
7194
|
+
return harnessDir === null ? { hard: false, source: "none" } : resolveCompassEnforcement(harnessDir);
|
|
7195
|
+
}
|
|
7196
|
+
function harnessRulesText(harnessDir, enforcement) {
|
|
7197
|
+
return [
|
|
7198
|
+
"This session runs under the Morning Star (mstar) harness: prompts and tool calls are governed by the harness gates and rules.",
|
|
7199
|
+
`enforcement: ${enforcement.hard ? "hard" : "soft"}`,
|
|
7200
|
+
`harness dir: ${stripInterpolationHazard(harnessDir ?? "none")}`,
|
|
7201
|
+
"Before non-trivial work, read the skill `skill://mstar-harness-core` — the authoritative harness entry point (mstar-harness-core first, then topic skills)."
|
|
7202
|
+
].join(`
|
|
7203
|
+
`);
|
|
7204
|
+
}
|
|
7205
|
+
function engineStatusProvider(ctx, resolver, bootHarnessDir) {
|
|
7206
|
+
const memo = new Map;
|
|
7207
|
+
return (context) => {
|
|
7208
|
+
const harnessDir = resolveAssemblyHarnessDir(context, resolver, bootHarnessDir);
|
|
7209
|
+
const now = Date.now();
|
|
7210
|
+
let entry = memo.get(harnessDir);
|
|
7211
|
+
if (entry === undefined || now - entry.builtAt >= DEFAULT_CATALOG_TTL_MS) {
|
|
7212
|
+
entry = { source: buildCatalogSources(ctx, harnessDir), builtAt: now };
|
|
7213
|
+
memo.set(harnessDir, entry);
|
|
7214
|
+
}
|
|
7215
|
+
return engineStatusSummary(entry.source);
|
|
7216
|
+
};
|
|
7217
|
+
}
|
|
7218
|
+
function engineStatusSummary(source) {
|
|
7219
|
+
const lines = [
|
|
7220
|
+
`mstar engine status: v${source.version} | harness ${stripInterpolationHazard(source.harnessDir ?? "none")} | enforcement ${source.enforcement.hard ? "hard" : "soft"}`
|
|
7221
|
+
];
|
|
7222
|
+
const iteration = source.iteration;
|
|
7223
|
+
if (iteration !== undefined) {
|
|
7224
|
+
const gate2 = iteration.gate;
|
|
7225
|
+
const codes = gate2.violations.map((v) => v.code).join(", ");
|
|
7226
|
+
lines.push(`iteration ${stripInterpolationHazard(iteration.iterationId)}: gate ${gate2.ok ? "PASS" : `FAIL (${codes})`} | transition ${gate2.transition} | all plans done ${gate2.all_plans_done}`);
|
|
7227
|
+
}
|
|
7228
|
+
const state = source.state;
|
|
7229
|
+
if (state !== null) {
|
|
7230
|
+
const plans = state.plans.length === 0 ? "none" : state.plans.map((p) => `${stripInterpolationHazard(p.id)}(${stripInterpolationHazard(p.status)})`).join(" ");
|
|
7231
|
+
const residuals = state.residuals.length === 0 ? "none" : state.residuals.map((r) => `${r.severity} ${r.count}`).join(", ");
|
|
7232
|
+
const leases = state.leases.length === 0 ? "none active" : state.leases.map((l) => `${stripInterpolationHazard(l.planId)} → ${stripInterpolationHazard(l.holder)}`).join("; ");
|
|
7233
|
+
lines.push(`plans: ${plans} | residuals: ${residuals} | leases: ${leases}`);
|
|
7234
|
+
if (state.direction !== null)
|
|
7235
|
+
lines.push(`direction: ${stripInterpolationHazard(state.direction)}`);
|
|
7236
|
+
}
|
|
7237
|
+
return lines.join(`
|
|
7238
|
+
`);
|
|
7239
|
+
}
|
|
7240
|
+
function errorMessage8(error) {
|
|
7241
|
+
return error instanceof Error ? error.message : String(error);
|
|
7242
|
+
}
|
|
7243
|
+
function log7(level, message) {
|
|
7244
|
+
try {
|
|
7245
|
+
harnessPromptLogSink(level, message);
|
|
7246
|
+
} catch {}
|
|
7247
|
+
}
|
|
7248
|
+
|
|
7249
|
+
// src/index.ts
|
|
7250
|
+
var name = "dsh";
|
|
7251
|
+
var inject = ["loader"];
|
|
7252
|
+
function packagedCommandsDir() {
|
|
7253
|
+
try {
|
|
7254
|
+
const dir = fileURLToPath3(new URL("../harness-commands", import.meta.url));
|
|
7255
|
+
return existsSync18(dir) ? dir : undefined;
|
|
7256
|
+
} catch {
|
|
7257
|
+
return;
|
|
7258
|
+
}
|
|
7259
|
+
}
|
|
7260
|
+
function commandFrontmatterField(frontmatter, label) {
|
|
7261
|
+
const match = new RegExp(`^${label}[ \\t]*:[ \\t]*(.+)$`, "m").exec(frontmatter);
|
|
7262
|
+
return match?.[1]?.trim();
|
|
7263
|
+
}
|
|
7264
|
+
function unquote2(value) {
|
|
7265
|
+
return value.length >= 2 && value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value;
|
|
7266
|
+
}
|
|
7267
|
+
function parseCommandMarkdown(content) {
|
|
7268
|
+
const lines = content.replace(/^\uFEFF/, "").split(/\r?\n/);
|
|
7269
|
+
if (lines.length === 0 || lines[0].trim() !== "---")
|
|
7270
|
+
return;
|
|
7271
|
+
let end = -1;
|
|
7272
|
+
for (let i = 1;i < lines.length; i++) {
|
|
7273
|
+
if (lines[i].trim() === "---") {
|
|
7274
|
+
end = i;
|
|
7275
|
+
break;
|
|
7276
|
+
}
|
|
7277
|
+
}
|
|
7278
|
+
if (end === -1)
|
|
7279
|
+
return;
|
|
7280
|
+
const frontmatter = lines.slice(1, end).join(`
|
|
7281
|
+
`);
|
|
7282
|
+
const name2 = commandFrontmatterField(frontmatter, "name");
|
|
7283
|
+
const description = commandFrontmatterField(frontmatter, "description");
|
|
7284
|
+
if (name2 === undefined || description === undefined)
|
|
7285
|
+
return;
|
|
7286
|
+
const input = commandFrontmatterField(frontmatter, "input");
|
|
7287
|
+
return {
|
|
7288
|
+
name: name2,
|
|
7289
|
+
description: unquote2(description),
|
|
7290
|
+
...input !== undefined && input !== "" ? { input: unquote2(input) } : {},
|
|
7291
|
+
body: lines.slice(end + 1).join(`
|
|
7292
|
+
`).trim()
|
|
7293
|
+
};
|
|
7294
|
+
}
|
|
7295
|
+
function registerMstarCommands(ctx) {
|
|
7296
|
+
const dir = packagedCommandsDir();
|
|
7297
|
+
if (dir === undefined)
|
|
7298
|
+
return;
|
|
7299
|
+
ctx.inject(["commands"], (commandsCtx) => {
|
|
7300
|
+
for (const file of readdirSync9(dir).sort()) {
|
|
7301
|
+
if (!file.endsWith(".md"))
|
|
7302
|
+
continue;
|
|
7303
|
+
const parsed = parseCommandMarkdown(readFileSync15(join20(dir, file), "utf8"));
|
|
7304
|
+
if (parsed === undefined)
|
|
7305
|
+
continue;
|
|
7306
|
+
commandsCtx.commands.register({
|
|
5639
7307
|
name: parsed.name,
|
|
5640
7308
|
description: parsed.description,
|
|
5641
7309
|
...parsed.input !== undefined ? { input: { hint: parsed.input } } : {},
|
|
@@ -5675,7 +7343,98 @@ function apply(ctx, config) {
|
|
|
5675
7343
|
else
|
|
5676
7344
|
logger.info(message);
|
|
5677
7345
|
});
|
|
7346
|
+
setDecorationLogger((level, message) => {
|
|
7347
|
+
const logger = ctx.logger(DECORATION_LOGGER);
|
|
7348
|
+
if (level === "debug")
|
|
7349
|
+
logger.debug(message);
|
|
7350
|
+
else if (level === "info")
|
|
7351
|
+
logger.info(message);
|
|
7352
|
+
else
|
|
7353
|
+
logger.warn(message);
|
|
7354
|
+
});
|
|
7355
|
+
setDecorationAgentsDir(packagedAgentsDir());
|
|
7356
|
+
setHarnessPromptLogger((level, message) => {
|
|
7357
|
+
const logger = ctx.logger(HARNESS_PROMPT_LOGGER);
|
|
7358
|
+
if (level === "debug")
|
|
7359
|
+
logger.debug(message);
|
|
7360
|
+
else
|
|
7361
|
+
logger.warn(message);
|
|
7362
|
+
});
|
|
7363
|
+
registerHarnessPrompt(ctx, { resolver });
|
|
7364
|
+
setAdvisoryLogger((level, message) => {
|
|
7365
|
+
const logger = ctx.logger(ADVISORY_LOGGER);
|
|
7366
|
+
if (level === "debug")
|
|
7367
|
+
logger.debug(message);
|
|
7368
|
+
else
|
|
7369
|
+
logger.warn(message);
|
|
7370
|
+
});
|
|
7371
|
+
let advisoryPassed = false;
|
|
7372
|
+
let advisoryPassInFlight = false;
|
|
7373
|
+
let advisoryGeneration = 0;
|
|
7374
|
+
const runAdvisoryPass = () => {
|
|
7375
|
+
if (advisoryPassed || advisoryPassInFlight)
|
|
7376
|
+
return;
|
|
7377
|
+
advisoryPassInFlight = true;
|
|
7378
|
+
const generation = advisoryGeneration;
|
|
7379
|
+
runFallbacksAdvisory(ctx, packagedAgentsDir()).then((ran) => {
|
|
7380
|
+
if (ran && generation === advisoryGeneration)
|
|
7381
|
+
advisoryPassed = true;
|
|
7382
|
+
}).finally(() => {
|
|
7383
|
+
advisoryPassInFlight = false;
|
|
7384
|
+
});
|
|
7385
|
+
};
|
|
7386
|
+
runAdvisoryPass();
|
|
7387
|
+
if (!fallbacksMounted(ctx)) {
|
|
7388
|
+
ctx.logger(SEEDS_LOGGER).debug("fallbacks not mounted at apply — mstar seeds inject child armed; declare fires when the llm-fallbacks service appears (apply/HMR)");
|
|
7389
|
+
}
|
|
7390
|
+
ctx.inject(["llm-fallbacks"], (serviceCtx) => {
|
|
7391
|
+
const service = fallbacksService(serviceCtx);
|
|
7392
|
+
if (service === undefined)
|
|
7393
|
+
return;
|
|
7394
|
+
declareMstarSeeds(service, {
|
|
7395
|
+
agentsDir: packagedAgentsDir(),
|
|
7396
|
+
log: (level, message) => {
|
|
7397
|
+
const logger = ctx.logger(SEEDS_LOGGER);
|
|
7398
|
+
if (level === "warn")
|
|
7399
|
+
logger.warn(message);
|
|
7400
|
+
else if (level === "error")
|
|
7401
|
+
logger.error(message);
|
|
7402
|
+
else
|
|
7403
|
+
logger.debug(message);
|
|
7404
|
+
}
|
|
7405
|
+
}).catch((error) => {
|
|
7406
|
+
ctx.logger(SEEDS_LOGGER).error(`mstar seeds declaration failed (contained — fallbacks taxonomy unchanged): ${error instanceof Error ? error.message : String(error)}`);
|
|
7407
|
+
});
|
|
7408
|
+
return () => {
|
|
7409
|
+
advisoryPassed = false;
|
|
7410
|
+
advisoryGeneration += 1;
|
|
7411
|
+
};
|
|
7412
|
+
});
|
|
5678
7413
|
registerSettleListener(ctx, config, pairing);
|
|
7414
|
+
setWorkflowLedgerLogger((level, message) => {
|
|
7415
|
+
const logger = ctx.logger(WORKFLOW_LEDGER_LOGGER);
|
|
7416
|
+
if (level === "warn")
|
|
7417
|
+
logger.warn(message);
|
|
7418
|
+
else
|
|
7419
|
+
logger.debug(message);
|
|
7420
|
+
});
|
|
7421
|
+
registerWorkflowLedger(ctx, resolver, adapter.workflowAskCache);
|
|
7422
|
+
setGoalBridgeLogger((level, message) => {
|
|
7423
|
+
const logger = ctx.logger(GOAL_BRIDGE_LOGGER);
|
|
7424
|
+
if (level === "debug")
|
|
7425
|
+
logger.debug(message);
|
|
7426
|
+
else
|
|
7427
|
+
logger.warn(message);
|
|
7428
|
+
});
|
|
7429
|
+
registerGoalBridge(ctx, resolver, config);
|
|
7430
|
+
setPlanModeBridgeLogger((level, message) => {
|
|
7431
|
+
const logger = ctx.logger(PLAN_MODE_BRIDGE_LOGGER);
|
|
7432
|
+
if (level === "debug")
|
|
7433
|
+
logger.debug(message);
|
|
7434
|
+
else
|
|
7435
|
+
logger.warn(message);
|
|
7436
|
+
});
|
|
7437
|
+
registerPlanModeBridge(ctx, resolver);
|
|
5679
7438
|
ctx.inject(["jobs"], (jobsCtx) => {
|
|
5680
7439
|
const jobs = jobsCtx.jobs;
|
|
5681
7440
|
try {
|
|
@@ -5696,7 +7455,7 @@ function apply(ctx, config) {
|
|
|
5696
7455
|
ctx.logger(DISPATCH_LOGGER).warn("Enforcement: hard is active but dispatchBinding is unset — the anti-recursion precheck is skipped (an Assignment whose Execute as equals the dispatching agent cannot be detected)");
|
|
5697
7456
|
}
|
|
5698
7457
|
if (effectiveHard && config.dispatchTools === undefined) {
|
|
5699
|
-
ctx.logger(DISPATCH_LOGGER).warn('Enforcement: hard is active but dispatchTools is unset — the dispatch gate matches the default tool
|
|
7458
|
+
ctx.logger(DISPATCH_LOGGER).warn('Enforcement: hard is active but dispatchTools is unset — the dispatch gate matches the default tool names "subagent" and "subagent_fork"; a deployment renaming either dsh subagent tool (toolName) without declaring dispatchTools silently disables the gate');
|
|
5700
7459
|
}
|
|
5701
7460
|
ctx.on("fs/write-intent", (target, actor, next) => writeIntentListener(ctx, resolver, config, adapter, target, actor, next), { prepend: true });
|
|
5702
7461
|
ctx.on("fs/edit-intent", (target, actor, next) => editIntentListener(ctx, resolver, config, adapter, target, actor, next), { prepend: true });
|
|
@@ -5706,6 +7465,10 @@ function apply(ctx, config) {
|
|
|
5706
7465
|
ctx.on("fs/write-intent", (target, actor, next) => seamWriteIntentListener(ctx, resolver, config, seam, target, actor, next), { prepend: true });
|
|
5707
7466
|
}
|
|
5708
7467
|
ctx.on("tools/pre-execute", (exec, next) => preExecuteListener(ctx, resolver, config, adapter, exec, next), { prepend: true });
|
|
7468
|
+
ctx.events.on("subagent/start", (info) => {
|
|
7469
|
+
runAdvisoryPass();
|
|
7470
|
+
decorateSubagentStart(ctx, config, info);
|
|
7471
|
+
});
|
|
5709
7472
|
const ttlMs = config.catalogTtlMs ?? DEFAULT_CATALOG_TTL_MS;
|
|
5710
7473
|
const explicitKey = bootHarnessDir !== null ? EXPLICIT_CACHE_KEY : undefined;
|
|
5711
7474
|
const catalogCache = new Map;
|
|
@@ -5723,6 +7486,11 @@ function apply(ctx, config) {
|
|
|
5723
7486
|
}
|
|
5724
7487
|
export {
|
|
5725
7488
|
skillLocalConfig,
|
|
7489
|
+
setDecorationLogger,
|
|
7490
|
+
setDecorationAgentsDir,
|
|
7491
|
+
setAdvisoryLogger,
|
|
7492
|
+
runFallbacksAdvisory,
|
|
7493
|
+
recordWorkflowVerdict,
|
|
5726
7494
|
recordSettle,
|
|
5727
7495
|
recordDispatch,
|
|
5728
7496
|
readAgentFlow,
|
|
@@ -5735,14 +7503,19 @@ export {
|
|
|
5735
7503
|
lintCompoundWrite,
|
|
5736
7504
|
lintAuditWrite,
|
|
5737
7505
|
inject,
|
|
7506
|
+
decorateSubagentStart,
|
|
5738
7507
|
apply,
|
|
5739
7508
|
SkillLintVetoError,
|
|
5740
7509
|
SeamVetoError,
|
|
5741
7510
|
SETTLE_SEAM,
|
|
7511
|
+
PERSONA_SECTION_ORDER,
|
|
7512
|
+
PERSONA_SECTION_NAME,
|
|
5742
7513
|
HarnessResolver,
|
|
5743
7514
|
DshMstar,
|
|
5744
7515
|
DshHostAdapter,
|
|
7516
|
+
DECORATION_LOGGER,
|
|
5745
7517
|
Config,
|
|
5746
7518
|
AGENT_FLOW_MAX_EVENTS,
|
|
5747
|
-
AGENT_FLOW_FILE
|
|
7519
|
+
AGENT_FLOW_FILE,
|
|
7520
|
+
ADVISORY_LOGGER
|
|
5748
7521
|
};
|