@mstar-harness/dsh 2.2.0 → 2.4.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 +124 -3
- package/README.zh.md +124 -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 +1865 -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-commands/iteration-drive.md +1 -1
- 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 +19 -2
- package/harness-skills/mstar-coding-behavior/SKILL.md +18 -0
- package/harness-skills/mstar-compound/SKILL.md +4 -0
- package/harness-skills/mstar-compound/references/compound-workflow.md +16 -0
- package/harness-skills/mstar-compound-refresh/SKILL.md +14 -0
- package/harness-skills/mstar-design-md/SKILL.md +1 -1
- package/harness-skills/mstar-dispatch-gates/SKILL.md +5 -1
- package/harness-skills/mstar-host/SKILL.md +20 -2
- package/harness-skills/mstar-iteration/SKILL.md +9 -1
- package/harness-skills/mstar-iteration/references/phase-4-5-pr-delivery.md +1 -0
- package/harness-skills/mstar-phase-gates/SKILL.md +6 -0
- package/harness-skills/mstar-plan-artifacts/SKILL.md +25 -3
- package/harness-skills/mstar-plan-artifacts/references/status-and-residuals.md +10 -1
- package/harness-skills/mstar-plan-conventions/SKILL.md +8 -0
- package/harness-skills/mstar-review-qc/SKILL.md +16 -0
- package/harness-skills/mstar-roles/SKILL.md +14 -1
- 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 +7 -3
- package/harness-skills/mstar-sdd/references/file-handoffs.md +2 -0
- package/harness-skills/mstar-skill-authoring/SKILL.md +24 -4
- 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/harness-skills/mstar-strategy/SKILL.md +14 -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;
|
|
@@ -3584,12 +3604,44 @@ var FIVE_QUESTION_SECTIONS = [
|
|
|
3584
3604
|
{ key: "references", label: "References", question: "additional resources to open when the main path is not enough" }
|
|
3585
3605
|
];
|
|
3586
3606
|
var HEADING_RE = /^#{1,6}\s+[^\r\n]+$/;
|
|
3587
|
-
function
|
|
3588
|
-
const headings =
|
|
3607
|
+
function collectHeadings(bodyText) {
|
|
3608
|
+
const headings = [];
|
|
3609
|
+
let inFence = false;
|
|
3610
|
+
for (const line of bodyText.split(/\r?\n/)) {
|
|
3611
|
+
if (line.startsWith("```") || line.startsWith("~~~")) {
|
|
3612
|
+
inFence = !inFence;
|
|
3613
|
+
continue;
|
|
3614
|
+
}
|
|
3615
|
+
if (!inFence && HEADING_RE.test(line)) {
|
|
3616
|
+
headings.push(line.replace(/^#{1,6}\s+/, "").trim().toLowerCase());
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
return headings;
|
|
3620
|
+
}
|
|
3621
|
+
var RUNTIME_HEADING_ALIASES = {
|
|
3622
|
+
workflow: ["process", "playbook"],
|
|
3623
|
+
"decision-rules": [
|
|
3624
|
+
"hard rules",
|
|
3625
|
+
"core rules",
|
|
3626
|
+
"rule",
|
|
3627
|
+
"gate",
|
|
3628
|
+
"not to do",
|
|
3629
|
+
"red flags",
|
|
3630
|
+
"反模式",
|
|
3631
|
+
"红线",
|
|
3632
|
+
"规则",
|
|
3633
|
+
"门禁"
|
|
3634
|
+
],
|
|
3635
|
+
evidence: ["output format", "证据"],
|
|
3636
|
+
references: ["dependencies", "关系"]
|
|
3637
|
+
};
|
|
3638
|
+
function lintFiveQuestion(bodyText, mode = "authoring") {
|
|
3639
|
+
const headings = collectHeadings(bodyText);
|
|
3589
3640
|
const violations = [];
|
|
3590
3641
|
for (const section of FIVE_QUESTION_SECTIONS) {
|
|
3591
3642
|
const label = section.label.toLowerCase();
|
|
3592
|
-
const
|
|
3643
|
+
const aliases = mode === "runtime" ? RUNTIME_HEADING_ALIASES[section.key] ?? [] : [];
|
|
3644
|
+
const covered = headings.some((heading) => heading.includes(label) || aliases.some((alias) => heading.includes(alias)));
|
|
3593
3645
|
if (!covered) {
|
|
3594
3646
|
violations.push(violation11("low", `skill-authoring.five-question.${section.key}`, `body does not answer "${section.question}" — no "${section.label}" section (mstar-skill-authoring § Body 必须回答的 5 问 / § 默认 Body 结构)`, `add a "## ${section.label}" section covering ${section.question}`));
|
|
3595
3647
|
}
|
|
@@ -3640,6 +3692,19 @@ import { existsSync as existsSync7 } from "node:fs";
|
|
|
3640
3692
|
import { resolve as resolve7 } from "node:path";
|
|
3641
3693
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3642
3694
|
var STATUS_FILE = "status.json";
|
|
3695
|
+
var PERSONA_INTERPOLATION_HAZARD = /\{\{[\s\S]*\}\}/;
|
|
3696
|
+
function stripInterpolationHazard(text) {
|
|
3697
|
+
let out = text;
|
|
3698
|
+
for (;; ) {
|
|
3699
|
+
const open = out.indexOf("{{");
|
|
3700
|
+
if (open < 0)
|
|
3701
|
+
return out;
|
|
3702
|
+
const close = out.indexOf("}}", open + 2);
|
|
3703
|
+
if (close < 0)
|
|
3704
|
+
return out;
|
|
3705
|
+
out = `${out.slice(0, open)}{ {${out.slice(open + 2, close)}} }${out.slice(close + 2)}`;
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3643
3708
|
var Config = import_schemastery.default.object({
|
|
3644
3709
|
harnessDir: import_schemastery.default.string(),
|
|
3645
3710
|
enforcement: import_schemastery.default.union(["hard", "soft"]),
|
|
@@ -3647,7 +3712,19 @@ var Config = import_schemastery.default.object({
|
|
|
3647
3712
|
dispatchBinding: import_schemastery.default.string().default(undefined),
|
|
3648
3713
|
skillRoots: import_schemastery.default.array(import_schemastery.default.string()).default(undefined),
|
|
3649
3714
|
bundledSkillDir: import_schemastery.default.string().default(undefined),
|
|
3650
|
-
catalogTtlMs: import_schemastery.default.number().default(undefined)
|
|
3715
|
+
catalogTtlMs: import_schemastery.default.number().default(undefined),
|
|
3716
|
+
roleMap: import_schemastery.default.dict(import_schemastery.default.string()).default(undefined),
|
|
3717
|
+
rolePersonas: import_schemastery.default.transform(import_schemastery.default.dict(import_schemastery.default.string()), (value) => {
|
|
3718
|
+
for (const [role, persona] of Object.entries(value)) {
|
|
3719
|
+
if (PERSONA_INTERPOLATION_HAZARD.test(persona)) {
|
|
3720
|
+
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)`, {});
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
return value;
|
|
3724
|
+
}).default(undefined),
|
|
3725
|
+
workflowGate: import_schemastery.default.union(["off", "warn", "ask", "hard"]).default("warn"),
|
|
3726
|
+
workflowNames: import_schemastery.default.array(import_schemastery.default.string()).default(undefined),
|
|
3727
|
+
maxGoalRounds: import_schemastery.default.number().default(undefined)
|
|
3651
3728
|
});
|
|
3652
3729
|
function formatViolation(violation12) {
|
|
3653
3730
|
return `[${violation12.severity}] ${violation12.code}: ${violation12.message}${violation12.fix !== undefined ? ` (fix: ${violation12.fix})` : ""}`;
|
|
@@ -3681,6 +3758,25 @@ function packagedSkillsDir() {
|
|
|
3681
3758
|
packagedSkillsDirMemo = dir;
|
|
3682
3759
|
return dir;
|
|
3683
3760
|
}
|
|
3761
|
+
function resolvePackagedAgentsDir(fileUrl) {
|
|
3762
|
+
for (const rel of ["../harness-agents", "../../harness-agents"]) {
|
|
3763
|
+
try {
|
|
3764
|
+
const dir = fileURLToPath2(new URL(rel, fileUrl));
|
|
3765
|
+
if (existsSync7(dir))
|
|
3766
|
+
return dir;
|
|
3767
|
+
} catch {}
|
|
3768
|
+
}
|
|
3769
|
+
return;
|
|
3770
|
+
}
|
|
3771
|
+
var packagedAgentsDirMemo;
|
|
3772
|
+
function packagedAgentsDir() {
|
|
3773
|
+
if (packagedAgentsDirMemo !== undefined)
|
|
3774
|
+
return packagedAgentsDirMemo;
|
|
3775
|
+
const dir = resolvePackagedAgentsDir(import.meta.url);
|
|
3776
|
+
if (dir !== undefined)
|
|
3777
|
+
packagedAgentsDirMemo = dir;
|
|
3778
|
+
return dir;
|
|
3779
|
+
}
|
|
3684
3780
|
|
|
3685
3781
|
class HarnessResolver {
|
|
3686
3782
|
explicit;
|
|
@@ -3745,8 +3841,99 @@ import { join as join10 } from "node:path";
|
|
|
3745
3841
|
// src/gates/dispatch.ts
|
|
3746
3842
|
import { existsSync as existsSync9 } from "node:fs";
|
|
3747
3843
|
import { basename as basename5, join as join6, resolve as resolve9 } from "node:path";
|
|
3844
|
+
|
|
3845
|
+
// src/gates/workflow-policy.ts
|
|
3846
|
+
var WORKFLOW_NAME_UNKNOWN_CODE = "workflow.name.unknown";
|
|
3847
|
+
var WORKFLOW_LEASE_UNCOVERED_CODE = "workflow.lease.uncovered";
|
|
3848
|
+
var WORKFLOW_NAME_CONTROL_CHARS = /[\u0000-\u001F\u007F]/g;
|
|
3849
|
+
function normalizeWorkflowName(value) {
|
|
3850
|
+
return value.replace(WORKFLOW_NAME_CONTROL_CHARS, "");
|
|
3851
|
+
}
|
|
3852
|
+
function workflowNameUnknown(config, metaName) {
|
|
3853
|
+
const names = config.workflowNames;
|
|
3854
|
+
if (names === undefined || names.length === 0)
|
|
3855
|
+
return true;
|
|
3856
|
+
return !names.some((entry) => normalizeWorkflowName(entry) === metaName);
|
|
3857
|
+
}
|
|
3858
|
+
|
|
3859
|
+
class WorkflowAskCache {
|
|
3860
|
+
decisions = new Map;
|
|
3861
|
+
asked = new Set;
|
|
3862
|
+
get(name) {
|
|
3863
|
+
return this.decisions.get(name);
|
|
3864
|
+
}
|
|
3865
|
+
record(name, decision) {
|
|
3866
|
+
this.decisions.set(normalizeWorkflowName(name), decision);
|
|
3867
|
+
}
|
|
3868
|
+
markAsked(name) {
|
|
3869
|
+
this.asked.add(normalizeWorkflowName(name));
|
|
3870
|
+
}
|
|
3871
|
+
wasAsked(name) {
|
|
3872
|
+
return this.asked.has(normalizeWorkflowName(name));
|
|
3873
|
+
}
|
|
3874
|
+
}
|
|
3875
|
+
function workflowPolicy(config, cache, input) {
|
|
3876
|
+
const mode = config.workflowGate ?? "warn";
|
|
3877
|
+
if (mode === "off")
|
|
3878
|
+
return { decision: "allow" };
|
|
3879
|
+
if (input.uncoveredPlanId !== undefined) {
|
|
3880
|
+
if (mode === "hard") {
|
|
3881
|
+
return {
|
|
3882
|
+
decision: "deny",
|
|
3883
|
+
code: WORKFLOW_LEASE_UNCOVERED_CODE,
|
|
3884
|
+
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)`
|
|
3885
|
+
};
|
|
3886
|
+
}
|
|
3887
|
+
return {
|
|
3888
|
+
decision: "warn",
|
|
3889
|
+
code: WORKFLOW_LEASE_UNCOVERED_CODE,
|
|
3890
|
+
reason: `plan "${input.uncoveredPlanId}" is InProgress without execution_lease coverage (workflowGate: warn — advisory only)`
|
|
3891
|
+
};
|
|
3892
|
+
}
|
|
3893
|
+
if (input.metaName === undefined) {
|
|
3894
|
+
return { decision: "allow" };
|
|
3895
|
+
}
|
|
3896
|
+
const name = input.metaName;
|
|
3897
|
+
if (!workflowNameUnknown(config, name))
|
|
3898
|
+
return { decision: "allow" };
|
|
3899
|
+
switch (mode) {
|
|
3900
|
+
case "warn":
|
|
3901
|
+
return {
|
|
3902
|
+
decision: "warn",
|
|
3903
|
+
code: WORKFLOW_NAME_UNKNOWN_CODE,
|
|
3904
|
+
reason: `workflow name "${name}" is not in the workflowNames allowlist (workflowGate: warn — advisory only)`
|
|
3905
|
+
};
|
|
3906
|
+
case "hard":
|
|
3907
|
+
return {
|
|
3908
|
+
decision: "deny",
|
|
3909
|
+
code: WORKFLOW_NAME_UNKNOWN_CODE,
|
|
3910
|
+
reason: `workflow gate (${input.tool}) vetoed: workflow name "${name}" is not in the workflowNames allowlist (workflowGate: hard)`
|
|
3911
|
+
};
|
|
3912
|
+
case "ask": {
|
|
3913
|
+
const cached = cache.get(name);
|
|
3914
|
+
if (cached === "allow")
|
|
3915
|
+
return { decision: "allow" };
|
|
3916
|
+
if (cached === "deny") {
|
|
3917
|
+
return {
|
|
3918
|
+
decision: "deny",
|
|
3919
|
+
code: WORKFLOW_NAME_UNKNOWN_CODE,
|
|
3920
|
+
reason: `workflow gate (${input.tool}) vetoed: workflow name "${name}" was denied earlier in this session (workflowGate: ask — cached decision)`
|
|
3921
|
+
};
|
|
3922
|
+
}
|
|
3923
|
+
cache.markAsked(name);
|
|
3924
|
+
return {
|
|
3925
|
+
decision: "ask",
|
|
3926
|
+
code: WORKFLOW_NAME_UNKNOWN_CODE,
|
|
3927
|
+
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)`
|
|
3928
|
+
};
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
|
|
3933
|
+
// src/gates/dispatch.ts
|
|
3748
3934
|
var DISPATCH_LOGGER = "mstar/dispatch-gate";
|
|
3749
|
-
var DEFAULT_DISPATCH_TOOLS = ["subagent"];
|
|
3935
|
+
var DEFAULT_DISPATCH_TOOLS = ["subagent", "subagent_fork"];
|
|
3936
|
+
var DEFAULT_WORKFLOW_TOOLS = ["workflow", "ralph"];
|
|
3750
3937
|
var ASSIGNMENT_HEADING_RE2 = /^#{1,6}\s+Assignment\s*$/m;
|
|
3751
3938
|
var ASSIGNMENT_FIELD_RE2 = /^[ \t]*(?:[-*][ \t]+)?\*{0,2}(Execute as|Delegation|Task category)\*{0,2}[ \t]*:[ \t]*(\S.*)$/gm;
|
|
3752
3939
|
function isAssignmentShaped2(assignmentText) {
|
|
@@ -3931,6 +4118,30 @@ function worktreeL1Violations(harnessDir, header) {
|
|
|
3931
4118
|
}
|
|
3932
4119
|
return l1PreDispatchCheck({ controlWorktreePath, leaseWorktreePath, leaseWorkingBranch, planId }).violations;
|
|
3933
4120
|
}
|
|
4121
|
+
function writableFanOutUncovered(harnessDir) {
|
|
4122
|
+
if (harnessDir === null)
|
|
4123
|
+
return { unreadable: false };
|
|
4124
|
+
const statusPath = join6(harnessDir, STATUS_FILE);
|
|
4125
|
+
if (!existsSync9(statusPath))
|
|
4126
|
+
return { unreadable: false };
|
|
4127
|
+
let doc;
|
|
4128
|
+
try {
|
|
4129
|
+
doc = readJson(statusPath);
|
|
4130
|
+
} catch {
|
|
4131
|
+
return { unreadable: true };
|
|
4132
|
+
}
|
|
4133
|
+
if (!Array.isArray(doc.plans))
|
|
4134
|
+
return { unreadable: true };
|
|
4135
|
+
for (const raw of doc.plans) {
|
|
4136
|
+
const row = asRecord(raw);
|
|
4137
|
+
if (row === undefined || row.status !== "InProgress")
|
|
4138
|
+
continue;
|
|
4139
|
+
const planId = typeof row.id === "string" && row.id !== "" ? row.id : typeof row.plan_id === "string" && row.plan_id !== "" ? row.plan_id : "in-progress-plan";
|
|
4140
|
+
if (!verifyPlanExecutionLease(row, planId).ok)
|
|
4141
|
+
return { uncoveredPlanId: planId, unreadable: false };
|
|
4142
|
+
}
|
|
4143
|
+
return { unreadable: false };
|
|
4144
|
+
}
|
|
3934
4145
|
function dispatchGateCore(config, harnessDir, prompt) {
|
|
3935
4146
|
const header = assignmentHeaderRegion(prompt);
|
|
3936
4147
|
const violations = [...worktreeL2Violations(header), ...worktreeL1Violations(harnessDir, header)];
|
|
@@ -3938,8 +4149,96 @@ function dispatchGateCore(config, harnessDir, prompt) {
|
|
|
3938
4149
|
violations.push(...composeDispatchGate(header, { agent: config.dispatchBinding ?? "", writable }).violations);
|
|
3939
4150
|
return { violations, writable };
|
|
3940
4151
|
}
|
|
4152
|
+
function workflowGateInputOf(exec) {
|
|
4153
|
+
const args = asRecord(exec.arguments);
|
|
4154
|
+
if (args === undefined)
|
|
4155
|
+
return;
|
|
4156
|
+
if (exec.name === "ralph") {
|
|
4157
|
+
const objective = typeof args.objective === "string" ? args.objective : undefined;
|
|
4158
|
+
if (objective === undefined)
|
|
4159
|
+
return;
|
|
4160
|
+
return { tool: exec.name, objective, exec };
|
|
4161
|
+
}
|
|
4162
|
+
if (exec.name === "workflow") {
|
|
4163
|
+
const meta = asRecord(args.meta);
|
|
4164
|
+
const metaName = typeof meta?.name === "string" ? normalizeWorkflowName(meta.name) : undefined;
|
|
4165
|
+
if (meta === undefined || metaName === undefined || metaName === "")
|
|
4166
|
+
return;
|
|
4167
|
+
return { tool: exec.name, metaName, exec };
|
|
4168
|
+
}
|
|
4169
|
+
return;
|
|
4170
|
+
}
|
|
4171
|
+
function gateWorkflow(ctx, harnessDir, config, adapter, exec) {
|
|
4172
|
+
const toolName = exec.name;
|
|
4173
|
+
if (!DEFAULT_WORKFLOW_TOOLS.includes(toolName))
|
|
4174
|
+
return;
|
|
4175
|
+
const mode = config.workflowGate ?? "warn";
|
|
4176
|
+
if (mode === "off")
|
|
4177
|
+
return;
|
|
4178
|
+
const input = workflowGateInputOf(exec);
|
|
4179
|
+
if (input === undefined) {
|
|
4180
|
+
ctx.logger(DISPATCH_LOGGER).warn(`workflow gate (${toolName}) (fail-open): malformed arguments — no ${toolName === "ralph" ? "objective" : "meta"} to gate on; call allowed`);
|
|
4181
|
+
return;
|
|
4182
|
+
}
|
|
4183
|
+
const pb = writableFanOutUncovered(harnessDir);
|
|
4184
|
+
if (pb.unreadable) {
|
|
4185
|
+
const statusLabel = harnessDir === null ? STATUS_FILE : join6(harnessDir, STATUS_FILE);
|
|
4186
|
+
ctx.logger(DISPATCH_LOGGER).warn(`workflow gate (${toolName}) (fail-open): cannot read ${statusLabel} — P-b lease attribution degraded; call allowed`);
|
|
4187
|
+
}
|
|
4188
|
+
const verdict = workflowPolicy(config, adapter.workflowAskCache, {
|
|
4189
|
+
...input,
|
|
4190
|
+
...pb.uncoveredPlanId !== undefined ? { uncoveredPlanId: pb.uncoveredPlanId } : {}
|
|
4191
|
+
});
|
|
4192
|
+
const recordVerdict = (v, code) => {
|
|
4193
|
+
if (harnessDir === null)
|
|
4194
|
+
return;
|
|
4195
|
+
adapter.recordWorkflowVerdict({
|
|
4196
|
+
harnessDir,
|
|
4197
|
+
exec,
|
|
4198
|
+
tool: input.tool,
|
|
4199
|
+
...input.metaName !== undefined ? { workflow: input.metaName } : {},
|
|
4200
|
+
...input.objective !== undefined ? { objective: input.objective } : {},
|
|
4201
|
+
mode,
|
|
4202
|
+
verdict: v,
|
|
4203
|
+
...code !== undefined ? { code } : {}
|
|
4204
|
+
});
|
|
4205
|
+
};
|
|
4206
|
+
switch (verdict.decision) {
|
|
4207
|
+
case "allow":
|
|
4208
|
+
recordVerdict("ok");
|
|
4209
|
+
return;
|
|
4210
|
+
case "warn":
|
|
4211
|
+
ctx.logger(DISPATCH_LOGGER).warn(`workflow gate (${toolName}) (advisory): ${verdict.reason}`);
|
|
4212
|
+
ctx.emit("mstar/dispatch-gate", {
|
|
4213
|
+
tool: toolName,
|
|
4214
|
+
role: "",
|
|
4215
|
+
result: {
|
|
4216
|
+
ok: false,
|
|
4217
|
+
violations: [{
|
|
4218
|
+
ok: false,
|
|
4219
|
+
severity: "medium",
|
|
4220
|
+
code: verdict.code,
|
|
4221
|
+
message: verdict.reason
|
|
4222
|
+
}]
|
|
4223
|
+
},
|
|
4224
|
+
hard: false
|
|
4225
|
+
});
|
|
4226
|
+
recordVerdict("advisory", verdict.code);
|
|
4227
|
+
return;
|
|
4228
|
+
case "ask":
|
|
4229
|
+
recordVerdict("ask", verdict.code);
|
|
4230
|
+
return { kind: "ask", reason: verdict.reason };
|
|
4231
|
+
case "deny":
|
|
4232
|
+
recordVerdict("denied", verdict.code);
|
|
4233
|
+
ctx.logger(DISPATCH_LOGGER).error(`workflow gate (${toolName}) vetoed: ${verdict.reason}`);
|
|
4234
|
+
return { kind: "deny", reason: verdict.reason };
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
3941
4237
|
function gateDispatch(ctx, harnessDir, config, adapter, exec) {
|
|
3942
4238
|
const toolName = exec.name;
|
|
4239
|
+
const workflowDecision = gateWorkflow(ctx, harnessDir, config, adapter, exec);
|
|
4240
|
+
if (workflowDecision !== undefined)
|
|
4241
|
+
return workflowDecision;
|
|
3943
4242
|
if (!(config.dispatchTools ?? [...DEFAULT_DISPATCH_TOOLS]).includes(toolName))
|
|
3944
4243
|
return;
|
|
3945
4244
|
const args = asRecord(exec.arguments);
|
|
@@ -4002,6 +4301,19 @@ var AGENT_FLOW_FILE = "agent-flow.jsonl";
|
|
|
4002
4301
|
var AGENT_FLOW_MAX_EVENTS = 500;
|
|
4003
4302
|
var AGENT_FLOW_DEFAULT_LIMIT = 50;
|
|
4004
4303
|
var AGENT_FLOW_SIZE_GATE_BYTES = 64 * 1024;
|
|
4304
|
+
var WORKFLOW_LEDGER_MAX_ID_LENGTH = 512;
|
|
4305
|
+
var WORKFLOW_LEDGER_MAX_LABEL_LENGTH = 512;
|
|
4306
|
+
var WORKFLOW_LEDGER_MAX_NAME_LENGTH = 1024;
|
|
4307
|
+
var WORKFLOW_LEDGER_TRUNCATION_MARKER = "…";
|
|
4308
|
+
var WORKFLOW_LEDGER_MAX_SEQ = 2 ** 31;
|
|
4309
|
+
function truncateLedgerField(value, cap) {
|
|
4310
|
+
const cps = Array.from(value);
|
|
4311
|
+
if (cps.length <= cap)
|
|
4312
|
+
return value;
|
|
4313
|
+
const keep = cap - WORKFLOW_LEDGER_TRUNCATION_MARKER.length;
|
|
4314
|
+
const prefix = keep > 0 ? cps.slice(0, keep).join("") : "";
|
|
4315
|
+
return prefix + WORKFLOW_LEDGER_TRUNCATION_MARKER;
|
|
4316
|
+
}
|
|
4005
4317
|
var AGENT_FLOW_LOGGER = "mstar/agent-flow";
|
|
4006
4318
|
var SETTLE_SEAM = "tools/post-execute";
|
|
4007
4319
|
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 +4452,53 @@ function recordSettle(input) {
|
|
|
4140
4452
|
log("error", `settle record failed (contained): ${errorMessage(error)}`);
|
|
4141
4453
|
}
|
|
4142
4454
|
}
|
|
4455
|
+
function recordWorkflowEvent(input) {
|
|
4456
|
+
try {
|
|
4457
|
+
appendEvent(input.harnessDir, input.event);
|
|
4458
|
+
try {
|
|
4459
|
+
invalidator?.(input.harnessDir);
|
|
4460
|
+
} catch (error) {
|
|
4461
|
+
log("error", `catalog invalidation failed (contained): ${errorMessage(error)}`);
|
|
4462
|
+
}
|
|
4463
|
+
return true;
|
|
4464
|
+
} catch (error) {
|
|
4465
|
+
log("error", `workflow record failed (contained — the workflow run proceeds): ${errorMessage(error)}`);
|
|
4466
|
+
return false;
|
|
4467
|
+
}
|
|
4468
|
+
}
|
|
4469
|
+
function recordWorkflowVerdict(input) {
|
|
4470
|
+
try {
|
|
4471
|
+
const agent = input.exec !== undefined ? agentOfExec(input.exec) : undefined;
|
|
4472
|
+
const event = {
|
|
4473
|
+
v: 1,
|
|
4474
|
+
ts: Date.now(),
|
|
4475
|
+
kind: "workflow-verdict",
|
|
4476
|
+
...agent !== undefined ? { agent } : {},
|
|
4477
|
+
tool: input.tool,
|
|
4478
|
+
...input.workflow !== undefined && input.workflow !== "" ? { workflow: truncateLedgerField(input.workflow, WORKFLOW_LEDGER_MAX_NAME_LENGTH) } : {},
|
|
4479
|
+
...input.objective !== undefined && input.objective !== "" ? { objective: truncateLedgerField(normalizeWorkflowName(input.objective), WORKFLOW_LEDGER_MAX_NAME_LENGTH) } : {},
|
|
4480
|
+
mode: input.mode,
|
|
4481
|
+
verdict: input.verdict,
|
|
4482
|
+
...input.code !== undefined && input.code !== "" ? { code: input.code } : {}
|
|
4483
|
+
};
|
|
4484
|
+
appendEvent(input.harnessDir, event);
|
|
4485
|
+
try {
|
|
4486
|
+
invalidator?.(input.harnessDir);
|
|
4487
|
+
} catch (error) {
|
|
4488
|
+
log("error", `catalog invalidation failed (contained): ${errorMessage(error)}`);
|
|
4489
|
+
}
|
|
4490
|
+
} catch (error) {
|
|
4491
|
+
log("error", `workflow verdict record failed (contained — the gate proceeds): ${errorMessage(error)}`);
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4143
4494
|
function eventFromUnknown(value) {
|
|
4144
4495
|
const rec = asRecord(value);
|
|
4145
4496
|
if (rec === undefined || rec.v !== 1 || typeof rec.ts !== "number" || !Number.isFinite(rec.ts))
|
|
4146
4497
|
return;
|
|
4147
4498
|
const kind = rec.kind;
|
|
4148
|
-
if (kind !== "dispatch" && kind !== "settle")
|
|
4499
|
+
if (kind !== "dispatch" && kind !== "settle" && kind !== "workflow-verdict" && kind !== "workflow-run" && kind !== "workflow-agent" && kind !== "workflow-run-end") {
|
|
4149
4500
|
return;
|
|
4501
|
+
}
|
|
4150
4502
|
const agent = typeof rec.agent === "string" && rec.agent !== "" ? rec.agent : undefined;
|
|
4151
4503
|
if (kind === "dispatch") {
|
|
4152
4504
|
if (typeof rec.role !== "string" || typeof rec.hard !== "boolean")
|
|
@@ -4167,19 +4519,92 @@ function eventFromUnknown(value) {
|
|
|
4167
4519
|
hard: rec.hard
|
|
4168
4520
|
};
|
|
4169
4521
|
}
|
|
4170
|
-
|
|
4171
|
-
|
|
4522
|
+
if (kind === "settle") {
|
|
4523
|
+
const outcome = rec.outcome;
|
|
4524
|
+
if (outcome !== "ok" && outcome !== "error" && outcome !== "denied")
|
|
4525
|
+
return;
|
|
4526
|
+
return {
|
|
4527
|
+
v: 1,
|
|
4528
|
+
ts: rec.ts,
|
|
4529
|
+
kind: "settle",
|
|
4530
|
+
...agent !== undefined ? { agent } : {},
|
|
4531
|
+
outcome,
|
|
4532
|
+
...typeof rec.durationMs === "number" && Number.isFinite(rec.durationMs) ? { durationMs: rec.durationMs } : {},
|
|
4533
|
+
...typeof rec.role === "string" ? { role: rec.role } : {},
|
|
4534
|
+
...typeof rec.planId === "string" && rec.planId !== "" ? { planId: rec.planId } : {},
|
|
4535
|
+
...typeof rec.taskId === "string" && rec.taskId !== "" ? { taskId: rec.taskId } : {}
|
|
4536
|
+
};
|
|
4537
|
+
}
|
|
4538
|
+
if (kind === "workflow-verdict") {
|
|
4539
|
+
if (rec.tool !== "workflow" && rec.tool !== "ralph")
|
|
4540
|
+
return;
|
|
4541
|
+
const mode = rec.mode;
|
|
4542
|
+
if (mode !== "off" && mode !== "warn" && mode !== "ask" && mode !== "hard")
|
|
4543
|
+
return;
|
|
4544
|
+
const verdict = rec.verdict;
|
|
4545
|
+
if (verdict !== "ok" && verdict !== "advisory" && verdict !== "denied" && verdict !== "ask")
|
|
4546
|
+
return;
|
|
4547
|
+
if (rec.tool === "workflow") {
|
|
4548
|
+
if (typeof rec.workflow !== "string" || rec.workflow === "")
|
|
4549
|
+
return;
|
|
4550
|
+
} else if (typeof rec.objective !== "string" || rec.objective === "") {
|
|
4551
|
+
return;
|
|
4552
|
+
}
|
|
4553
|
+
return {
|
|
4554
|
+
v: 1,
|
|
4555
|
+
ts: rec.ts,
|
|
4556
|
+
kind: "workflow-verdict",
|
|
4557
|
+
...agent !== undefined ? { agent } : {},
|
|
4558
|
+
tool: rec.tool,
|
|
4559
|
+
...typeof rec.workflow === "string" && rec.workflow !== "" ? { workflow: truncateLedgerField(rec.workflow, WORKFLOW_LEDGER_MAX_NAME_LENGTH) } : {},
|
|
4560
|
+
...typeof rec.objective === "string" && rec.objective !== "" ? { objective: truncateLedgerField(rec.objective, WORKFLOW_LEDGER_MAX_NAME_LENGTH) } : {},
|
|
4561
|
+
mode,
|
|
4562
|
+
verdict,
|
|
4563
|
+
...typeof rec.code === "string" && rec.code !== "" ? { code: rec.code } : {}
|
|
4564
|
+
};
|
|
4565
|
+
}
|
|
4566
|
+
if (typeof rec.runId !== "string" || rec.runId === "" || rec.runId.length > WORKFLOW_LEDGER_MAX_ID_LENGTH)
|
|
4567
|
+
return;
|
|
4568
|
+
if (kind === "workflow-run") {
|
|
4569
|
+
if (typeof rec.name !== "string" || rec.name === "")
|
|
4570
|
+
return;
|
|
4571
|
+
return {
|
|
4572
|
+
v: 1,
|
|
4573
|
+
ts: rec.ts,
|
|
4574
|
+
kind: "workflow-run",
|
|
4575
|
+
...agent !== undefined ? { agent } : {},
|
|
4576
|
+
runId: rec.runId,
|
|
4577
|
+
name: truncateLedgerField(rec.name, WORKFLOW_LEDGER_MAX_NAME_LENGTH)
|
|
4578
|
+
};
|
|
4579
|
+
}
|
|
4580
|
+
if (kind === "workflow-agent") {
|
|
4581
|
+
if (typeof rec.seq !== "number" || !Number.isInteger(rec.seq) || rec.seq < 1 || rec.seq >= WORKFLOW_LEDGER_MAX_SEQ) {
|
|
4582
|
+
return;
|
|
4583
|
+
}
|
|
4584
|
+
if (typeof rec.label !== "string" || rec.label === "")
|
|
4585
|
+
return;
|
|
4586
|
+
if (typeof rec.childId !== "string" || rec.childId === "" || rec.childId.length > WORKFLOW_LEDGER_MAX_ID_LENGTH)
|
|
4587
|
+
return;
|
|
4588
|
+
return {
|
|
4589
|
+
v: 1,
|
|
4590
|
+
ts: rec.ts,
|
|
4591
|
+
kind: "workflow-agent",
|
|
4592
|
+
runId: rec.runId,
|
|
4593
|
+
seq: rec.seq,
|
|
4594
|
+
label: truncateLedgerField(rec.label, WORKFLOW_LEDGER_MAX_LABEL_LENGTH),
|
|
4595
|
+
...typeof rec.phase === "string" ? { phase: truncateLedgerField(rec.phase, WORKFLOW_LEDGER_MAX_LABEL_LENGTH) } : {},
|
|
4596
|
+
childId: rec.childId
|
|
4597
|
+
};
|
|
4598
|
+
}
|
|
4599
|
+
const stopReason = rec.stopReason;
|
|
4600
|
+
if (stopReason !== "completed" && stopReason !== "cancelled" && stopReason !== "error")
|
|
4172
4601
|
return;
|
|
4173
4602
|
return {
|
|
4174
4603
|
v: 1,
|
|
4175
4604
|
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 } : {}
|
|
4605
|
+
kind: "workflow-run-end",
|
|
4606
|
+
runId: rec.runId,
|
|
4607
|
+
stopReason
|
|
4183
4608
|
};
|
|
4184
4609
|
}
|
|
4185
4610
|
function eventView(event) {
|
|
@@ -4196,6 +4621,65 @@ function eventView(event) {
|
|
|
4196
4621
|
...event.hard !== undefined ? { hard: event.hard } : {}
|
|
4197
4622
|
};
|
|
4198
4623
|
}
|
|
4624
|
+
if (event.kind === "workflow-run") {
|
|
4625
|
+
return {
|
|
4626
|
+
ts: event.ts,
|
|
4627
|
+
kind: "workflow-run",
|
|
4628
|
+
agent: event.agent ?? null,
|
|
4629
|
+
role: "",
|
|
4630
|
+
planId: null,
|
|
4631
|
+
taskId: null,
|
|
4632
|
+
taskCategory: null,
|
|
4633
|
+
runId: event.runId,
|
|
4634
|
+
name: event.name
|
|
4635
|
+
};
|
|
4636
|
+
}
|
|
4637
|
+
if (event.kind === "workflow-agent") {
|
|
4638
|
+
return {
|
|
4639
|
+
ts: event.ts,
|
|
4640
|
+
kind: "workflow-agent",
|
|
4641
|
+
agent: null,
|
|
4642
|
+
role: "",
|
|
4643
|
+
planId: null,
|
|
4644
|
+
taskId: null,
|
|
4645
|
+
taskCategory: null,
|
|
4646
|
+
runId: event.runId,
|
|
4647
|
+
seq: event.seq,
|
|
4648
|
+
label: event.label,
|
|
4649
|
+
...event.phase !== undefined ? { phase: event.phase } : {},
|
|
4650
|
+
childId: event.childId
|
|
4651
|
+
};
|
|
4652
|
+
}
|
|
4653
|
+
if (event.kind === "workflow-run-end") {
|
|
4654
|
+
return {
|
|
4655
|
+
ts: event.ts,
|
|
4656
|
+
kind: "workflow-run-end",
|
|
4657
|
+
agent: null,
|
|
4658
|
+
role: "",
|
|
4659
|
+
planId: null,
|
|
4660
|
+
taskId: null,
|
|
4661
|
+
taskCategory: null,
|
|
4662
|
+
runId: event.runId,
|
|
4663
|
+
stopReason: event.stopReason
|
|
4664
|
+
};
|
|
4665
|
+
}
|
|
4666
|
+
if (event.kind === "workflow-verdict") {
|
|
4667
|
+
return {
|
|
4668
|
+
ts: event.ts,
|
|
4669
|
+
kind: "workflow-verdict",
|
|
4670
|
+
agent: event.agent ?? null,
|
|
4671
|
+
role: "",
|
|
4672
|
+
planId: null,
|
|
4673
|
+
taskId: null,
|
|
4674
|
+
taskCategory: null,
|
|
4675
|
+
tool: event.tool,
|
|
4676
|
+
...event.workflow !== undefined ? { workflow: event.workflow } : {},
|
|
4677
|
+
...event.objective !== undefined ? { objective: event.objective } : {},
|
|
4678
|
+
mode: event.mode,
|
|
4679
|
+
verdict: event.verdict,
|
|
4680
|
+
...event.code !== undefined ? { code: event.code } : {}
|
|
4681
|
+
};
|
|
4682
|
+
}
|
|
4199
4683
|
return {
|
|
4200
4684
|
ts: event.ts,
|
|
4201
4685
|
kind: "settle",
|
|
@@ -4212,8 +4696,8 @@ function eventView(event) {
|
|
|
4212
4696
|
function summaryOf(events) {
|
|
4213
4697
|
const counts = new Map;
|
|
4214
4698
|
for (const event of events) {
|
|
4215
|
-
const role = event.kind === "dispatch" ? event.role : "";
|
|
4216
|
-
const outcome = event.kind === "dispatch" ? event.verdict : event.outcome;
|
|
4699
|
+
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" : "";
|
|
4700
|
+
const outcome = event.kind === "dispatch" ? event.verdict : event.kind === "workflow-verdict" ? event.verdict : event.kind === "settle" ? event.outcome : event.kind;
|
|
4217
4701
|
const key = `${role}\x00${outcome}`;
|
|
4218
4702
|
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
4219
4703
|
}
|
|
@@ -4832,6 +5316,18 @@ function stripFrontmatter(text) {
|
|
|
4832
5316
|
}
|
|
4833
5317
|
return text;
|
|
4834
5318
|
}
|
|
5319
|
+
var EPHEMERAL_CODES = {
|
|
5320
|
+
"task-artifact": "skill.ephemeral.task-artifact",
|
|
5321
|
+
"sdd-deeplink": "skill.ephemeral.sdd-deeplink"
|
|
5322
|
+
};
|
|
5323
|
+
function ephemeralViolation(citation) {
|
|
5324
|
+
return {
|
|
5325
|
+
ok: false,
|
|
5326
|
+
severity: "medium",
|
|
5327
|
+
code: EPHEMERAL_CODES[citation.kind],
|
|
5328
|
+
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)`
|
|
5329
|
+
};
|
|
5330
|
+
}
|
|
4835
5331
|
function lintSkillDoc(doc) {
|
|
4836
5332
|
const violations = [];
|
|
4837
5333
|
const frontmatter = lintSkillFrontmatter(doc);
|
|
@@ -4840,6 +5336,7 @@ function lintSkillDoc(doc) {
|
|
|
4840
5336
|
const body = lintFiveQuestion(stripFrontmatter(doc));
|
|
4841
5337
|
if (!body.ok)
|
|
4842
5338
|
violations.push(...body.violations);
|
|
5339
|
+
violations.push(...findEphemeralCitations(doc).map(ephemeralViolation));
|
|
4843
5340
|
return violations.length === 0 ? { ok: true, violations } : { ok: false, violations };
|
|
4844
5341
|
}
|
|
4845
5342
|
function lintSkillWrite(doc, options) {
|
|
@@ -5512,12 +6009,14 @@ class DshHostAdapter extends Service2 {
|
|
|
5512
6009
|
resolver;
|
|
5513
6010
|
config;
|
|
5514
6011
|
pairing;
|
|
6012
|
+
workflowAskCache;
|
|
5515
6013
|
logSink;
|
|
5516
6014
|
constructor(ctx, options) {
|
|
5517
6015
|
super(ctx, "dshHostAdapter");
|
|
5518
6016
|
this.resolver = options.resolver;
|
|
5519
6017
|
this.config = options.config;
|
|
5520
6018
|
this.pairing = options.pairing;
|
|
6019
|
+
this.workflowAskCache = options.workflowAskCache ?? new WorkflowAskCache;
|
|
5521
6020
|
this.logSink = options.log ?? ((level, msg) => {
|
|
5522
6021
|
const logger = ctx.logger(HOST_LOGGER);
|
|
5523
6022
|
if (level === "warn")
|
|
@@ -5558,6 +6057,9 @@ class DshHostAdapter extends Service2 {
|
|
|
5558
6057
|
}
|
|
5559
6058
|
return { ok: violations.length === 0, violations };
|
|
5560
6059
|
}
|
|
6060
|
+
recordWorkflowVerdict(input) {
|
|
6061
|
+
recordWorkflowVerdict(input);
|
|
6062
|
+
}
|
|
5561
6063
|
async beforeStatusWrite(path, doc) {
|
|
5562
6064
|
const gate2 = doc !== undefined ? validateStatusValue(doc) : this.statusGate(path);
|
|
5563
6065
|
if (!gate2.ok) {
|
|
@@ -5578,28 +6080,91 @@ class DshHostAdapter extends Service2 {
|
|
|
5578
6080
|
}
|
|
5579
6081
|
}
|
|
5580
6082
|
|
|
5581
|
-
// src/
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
6083
|
+
// src/gates/agent-personas.ts
|
|
6084
|
+
import { readFileSync as readFileSync12, readdirSync as readdirSync7, statSync as statSync4 } from "node:fs";
|
|
6085
|
+
import { join as join16, resolve as resolve14, sep as sep4 } from "node:path";
|
|
6086
|
+
var ROLE_ID_PATTERN = /^[a-z0-9-]{1,32}$/;
|
|
6087
|
+
var defaultCache = new Map;
|
|
6088
|
+
function personaFor(roleId, lookup, warn) {
|
|
6089
|
+
const configured = lookup.rolePersonas?.[roleId];
|
|
6090
|
+
if (configured !== undefined && configured !== "")
|
|
6091
|
+
return { text: configured, source: "config" };
|
|
6092
|
+
if (lookup.agentsDir === undefined)
|
|
6093
|
+
return;
|
|
6094
|
+
const text = defaultFromMirror(lookup.agentsDir, roleId, warn);
|
|
6095
|
+
if (text === undefined || text.trim() === "")
|
|
6096
|
+
return;
|
|
6097
|
+
return { text, source: "default" };
|
|
6098
|
+
}
|
|
6099
|
+
function subagentRoleIds(agentsDir) {
|
|
6100
|
+
let names;
|
|
5585
6101
|
try {
|
|
5586
|
-
|
|
5587
|
-
return existsSync16(dir) ? dir : undefined;
|
|
6102
|
+
names = readdirSync7(agentsDir);
|
|
5588
6103
|
} catch {
|
|
5589
|
-
return;
|
|
6104
|
+
return [];
|
|
6105
|
+
}
|
|
6106
|
+
const ids = [];
|
|
6107
|
+
for (const name of names) {
|
|
6108
|
+
if (!name.endsWith(".md"))
|
|
6109
|
+
continue;
|
|
6110
|
+
const roleId = name.slice(0, -3);
|
|
6111
|
+
let content;
|
|
6112
|
+
try {
|
|
6113
|
+
content = readFileSync12(join16(agentsDir, name), "utf8");
|
|
6114
|
+
} catch {
|
|
6115
|
+
continue;
|
|
6116
|
+
}
|
|
6117
|
+
const mode = parseShellFrontmatter(content).mode;
|
|
6118
|
+
if (mode === undefined || mode === "subagent")
|
|
6119
|
+
ids.push(roleId);
|
|
5590
6120
|
}
|
|
6121
|
+
return ids.sort();
|
|
5591
6122
|
}
|
|
5592
|
-
function
|
|
5593
|
-
|
|
5594
|
-
|
|
6123
|
+
function defaultFromMirror(agentsDir, roleId, warn) {
|
|
6124
|
+
if (!ROLE_ID_PATTERN.test(roleId))
|
|
6125
|
+
return;
|
|
6126
|
+
const root = resolve14(agentsDir);
|
|
6127
|
+
const file = resolve14(agentsDir, `${roleId}.md`);
|
|
6128
|
+
if (!file.startsWith(root + sep4))
|
|
6129
|
+
return;
|
|
6130
|
+
let stat;
|
|
6131
|
+
try {
|
|
6132
|
+
stat = statSync4(file);
|
|
6133
|
+
} catch {
|
|
6134
|
+
return;
|
|
6135
|
+
}
|
|
6136
|
+
if (!stat.isFile())
|
|
6137
|
+
return;
|
|
6138
|
+
const hit = defaultCache.get(file);
|
|
6139
|
+
if (hit !== undefined && hit.mtimeMs === stat.mtimeMs)
|
|
6140
|
+
return hit.text;
|
|
6141
|
+
let content;
|
|
6142
|
+
try {
|
|
6143
|
+
content = readFileSync12(file, "utf8");
|
|
6144
|
+
} catch {
|
|
6145
|
+
return;
|
|
6146
|
+
}
|
|
6147
|
+
const text = extractShellPersona(content, roleId, warn);
|
|
6148
|
+
defaultCache.set(file, { mtimeMs: stat.mtimeMs, text });
|
|
6149
|
+
return text;
|
|
5595
6150
|
}
|
|
5596
|
-
function
|
|
5597
|
-
|
|
6151
|
+
function extractShellPersona(content, roleId, warn) {
|
|
6152
|
+
const parsed = parseShellFrontmatter(content);
|
|
6153
|
+
const description = parsed.description;
|
|
6154
|
+
if (description === undefined || description.trim() === "")
|
|
6155
|
+
return;
|
|
6156
|
+
if (parsed.mode !== undefined && parsed.mode !== "subagent")
|
|
6157
|
+
return;
|
|
6158
|
+
if (PERSONA_INTERPOLATION_HAZARD.test(description)) {
|
|
6159
|
+
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)`);
|
|
6160
|
+
return;
|
|
6161
|
+
}
|
|
6162
|
+
return description;
|
|
5598
6163
|
}
|
|
5599
|
-
function
|
|
6164
|
+
function parseShellFrontmatter(content) {
|
|
5600
6165
|
const lines = content.replace(/^\uFEFF/, "").split(/\r?\n/);
|
|
5601
6166
|
if (lines.length === 0 || lines[0].trim() !== "---")
|
|
5602
|
-
return;
|
|
6167
|
+
return {};
|
|
5603
6168
|
let end = -1;
|
|
5604
6169
|
for (let i = 1;i < lines.length; i++) {
|
|
5605
6170
|
if (lines[i].trim() === "---") {
|
|
@@ -5608,31 +6173,1166 @@ function parseCommandMarkdown(content) {
|
|
|
5608
6173
|
}
|
|
5609
6174
|
}
|
|
5610
6175
|
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
|
-
|
|
6176
|
+
return {};
|
|
6177
|
+
const frontmatter = lines.slice(1, end);
|
|
6178
|
+
const result = {};
|
|
6179
|
+
for (let i = 0;i < frontmatter.length; i++) {
|
|
6180
|
+
const line = frontmatter[i];
|
|
6181
|
+
const key = /^([A-Za-z][A-Za-z0-9_-]*)[ \t]*:[ \t]*(.*)$/.exec(line);
|
|
6182
|
+
if (key === null)
|
|
6183
|
+
continue;
|
|
6184
|
+
const label = key[1];
|
|
6185
|
+
const rest = key[2];
|
|
6186
|
+
if (label === "description") {
|
|
6187
|
+
const block = /^\|[+-]?(?<indent>\d*)[ \t]*$/.exec(rest);
|
|
6188
|
+
result.description = block !== null ? extractBlockScalar(frontmatter, i + 1, block.groups?.indent) : unquote(rest.trim());
|
|
6189
|
+
} else if (label === "mode") {
|
|
6190
|
+
result.mode = unquote(rest.trim());
|
|
6191
|
+
}
|
|
6192
|
+
}
|
|
6193
|
+
return result;
|
|
5626
6194
|
}
|
|
5627
|
-
function
|
|
5628
|
-
const
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
6195
|
+
function extractBlockScalar(frontmatter, start, explicitIndent) {
|
|
6196
|
+
const blockLines = [];
|
|
6197
|
+
for (let i = start;i < frontmatter.length; i++) {
|
|
6198
|
+
const line = frontmatter[i];
|
|
6199
|
+
if (line.trim() === "") {
|
|
6200
|
+
blockLines.push("");
|
|
6201
|
+
continue;
|
|
6202
|
+
}
|
|
6203
|
+
if (/^[ \t]/.test(line)) {
|
|
6204
|
+
blockLines.push(line);
|
|
6205
|
+
continue;
|
|
6206
|
+
}
|
|
6207
|
+
break;
|
|
6208
|
+
}
|
|
6209
|
+
let indent = explicitIndent !== undefined && explicitIndent !== "" ? Number(explicitIndent) : undefined;
|
|
6210
|
+
if (indent === undefined) {
|
|
6211
|
+
const first = blockLines.find((line) => line.trim() !== "");
|
|
6212
|
+
indent = first !== undefined ? /^[ \t]*/.exec(first)[0].length : 0;
|
|
6213
|
+
}
|
|
6214
|
+
return blockLines.map((line) => line.trim() === "" ? "" : line.slice(Math.min(indent, line.length))).join(`
|
|
6215
|
+
`).replace(/\n+$/, "");
|
|
6216
|
+
}
|
|
6217
|
+
function unquote(value) {
|
|
6218
|
+
return value.length >= 2 && value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value;
|
|
6219
|
+
}
|
|
6220
|
+
|
|
6221
|
+
// src/gates/fallbacks-probe.ts
|
|
6222
|
+
var FALLBACKS_ENTRY_NAME = "dsh-llm-fallbacks";
|
|
6223
|
+
function fallbacksService(ctx) {
|
|
6224
|
+
return ctx.get("llm-fallbacks");
|
|
6225
|
+
}
|
|
6226
|
+
function fallbacksEntry(ctx) {
|
|
6227
|
+
const loader = ctx.get("loader");
|
|
6228
|
+
if (loader === undefined)
|
|
6229
|
+
return;
|
|
6230
|
+
for (const entry of loader.entries()) {
|
|
6231
|
+
if (entry.options.name !== FALLBACKS_ENTRY_NAME)
|
|
6232
|
+
continue;
|
|
6233
|
+
if (entry.options.group)
|
|
6234
|
+
continue;
|
|
6235
|
+
if (entry.disabled)
|
|
6236
|
+
continue;
|
|
6237
|
+
return entry;
|
|
6238
|
+
}
|
|
6239
|
+
return;
|
|
6240
|
+
}
|
|
6241
|
+
function fallbacksMounted(ctx) {
|
|
6242
|
+
if (fallbacksService(ctx) !== undefined)
|
|
6243
|
+
return true;
|
|
6244
|
+
const entry = fallbacksEntry(ctx);
|
|
6245
|
+
if (entry === undefined)
|
|
6246
|
+
return false;
|
|
6247
|
+
return entry.fiber !== undefined;
|
|
6248
|
+
}
|
|
6249
|
+
|
|
6250
|
+
// src/gates/fallbacks-decoration.ts
|
|
6251
|
+
var DECORATION_LOGGER = "mstar/subagent-decoration";
|
|
6252
|
+
var PERSONA_SECTION_NAME = "mstar:role-persona";
|
|
6253
|
+
var PERSONA_SECTION_ORDER = 1;
|
|
6254
|
+
var decorationLogSink = () => {};
|
|
6255
|
+
function setDecorationLogger(sink) {
|
|
6256
|
+
const prior = decorationLogSink;
|
|
6257
|
+
decorationLogSink = sink;
|
|
6258
|
+
return prior;
|
|
6259
|
+
}
|
|
6260
|
+
var decorationAgentsDir;
|
|
6261
|
+
var mirrorAbsentDebugged = false;
|
|
6262
|
+
function setDecorationAgentsDir(dir) {
|
|
6263
|
+
const prior = decorationAgentsDir;
|
|
6264
|
+
decorationAgentsDir = dir;
|
|
6265
|
+
mirrorAbsentDebugged = false;
|
|
6266
|
+
return prior;
|
|
6267
|
+
}
|
|
6268
|
+
function seededTaskPrompt(child) {
|
|
6269
|
+
for (const event of child.session.events) {
|
|
6270
|
+
if (event.type !== "user/message")
|
|
6271
|
+
continue;
|
|
6272
|
+
const text = event.data.content.filter((block) => block.type === "text").map((block) => block.text).join(`
|
|
6273
|
+
`);
|
|
6274
|
+
if (text.trim().length > 0)
|
|
6275
|
+
return text;
|
|
6276
|
+
}
|
|
6277
|
+
return;
|
|
6278
|
+
}
|
|
6279
|
+
function decorateSubagentStart(ctx, config, info) {
|
|
6280
|
+
try {
|
|
6281
|
+
const personas = config.rolePersonas;
|
|
6282
|
+
const agentsDir = decorationAgentsDir;
|
|
6283
|
+
if ((personas == null || Object.keys(personas).length === 0) && agentsDir === undefined)
|
|
6284
|
+
return;
|
|
6285
|
+
const agents = ctx.get("agents");
|
|
6286
|
+
if (agents === undefined) {
|
|
6287
|
+
log2("debug", "agents service absent — mstar:role-persona decoration skipped (composition without dsh-agent)");
|
|
6288
|
+
return;
|
|
6289
|
+
}
|
|
6290
|
+
const child = agents.get(info.id);
|
|
6291
|
+
if (child === undefined)
|
|
6292
|
+
return;
|
|
6293
|
+
const taskPrompt = seededTaskPrompt(child);
|
|
6294
|
+
if (taskPrompt === undefined)
|
|
6295
|
+
return;
|
|
6296
|
+
const executeAs = parseAssignmentFields(assignmentHeaderRegion(taskPrompt)).executeAs;
|
|
6297
|
+
if (executeAs === undefined)
|
|
6298
|
+
return;
|
|
6299
|
+
const persona = personaFor(executeAs, { rolePersonas: config.rolePersonas, agentsDir }, (message) => log2("warn", message));
|
|
6300
|
+
if (persona === undefined) {
|
|
6301
|
+
if (agentsDir === undefined && !mirrorAbsentDebugged) {
|
|
6302
|
+
mirrorAbsentDebugged = true;
|
|
6303
|
+
log2("debug", "harness-agents mirror absent — mstar:role-persona skipped (config-only lookups; mirror defaults unavailable)");
|
|
6304
|
+
}
|
|
6305
|
+
return;
|
|
6306
|
+
}
|
|
6307
|
+
child.ctx.systemPrompt.section({
|
|
6308
|
+
name: PERSONA_SECTION_NAME,
|
|
6309
|
+
order: PERSONA_SECTION_ORDER,
|
|
6310
|
+
text: persona.text
|
|
6311
|
+
});
|
|
6312
|
+
if (fallbacksMounted(ctx)) {
|
|
6313
|
+
log2("info", `dsh-llm-fallbacks mounted (v${fallbacksService(ctx)?.version ?? "unknown"}) — mstar:role-persona injected for role '${executeAs}'`);
|
|
6314
|
+
} else {
|
|
6315
|
+
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)`);
|
|
6316
|
+
}
|
|
6317
|
+
} catch (error) {
|
|
6318
|
+
log2("warn", `mstar:role-persona decoration aborted (degraded — subagent dispatch unaffected): ${errorMessage2(error)}`);
|
|
6319
|
+
}
|
|
6320
|
+
}
|
|
6321
|
+
function errorMessage2(error) {
|
|
6322
|
+
return error instanceof Error ? error.message : String(error);
|
|
6323
|
+
}
|
|
6324
|
+
function log2(level, message) {
|
|
6325
|
+
try {
|
|
6326
|
+
decorationLogSink(level, message);
|
|
6327
|
+
} catch {}
|
|
6328
|
+
}
|
|
6329
|
+
|
|
6330
|
+
// src/gates/workflow-ledger.ts
|
|
6331
|
+
import { readFileSync as readFileSync13, renameSync as renameSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
6332
|
+
import { join as join17 } from "node:path";
|
|
6333
|
+
var WORKFLOW_LEDGER_LOGGER = "mstar/workflow-ledger";
|
|
6334
|
+
var TOOL_WORKFLOW_RUN_START = "tool-workflow/run-start";
|
|
6335
|
+
var TOOL_WORKFLOW_AGENT_START = "tool-workflow/agent-start";
|
|
6336
|
+
var TOOL_WORKFLOW_AGENT_END = "tool-workflow/agent-end";
|
|
6337
|
+
var TOOL_WORKFLOW_RUN_END = "tool-workflow/run-end";
|
|
6338
|
+
var DEPTH_ADVISORY_THRESHOLD = 2;
|
|
6339
|
+
var WORKFLOW_LEDGER_WATERMARK_FILE = "workflow-ledger-cursors.json";
|
|
6340
|
+
var WORKFLOW_LEDGER_WATERMARK_MAX_SESSIONS = 256;
|
|
6341
|
+
var workflowLedgerLogSink = () => {};
|
|
6342
|
+
function setWorkflowLedgerLogger(sink) {
|
|
6343
|
+
const prior = workflowLedgerLogSink;
|
|
6344
|
+
workflowLedgerLogSink = sink;
|
|
6345
|
+
return prior;
|
|
6346
|
+
}
|
|
6347
|
+
function log3(level, message) {
|
|
6348
|
+
try {
|
|
6349
|
+
workflowLedgerLogSink(level, message);
|
|
6350
|
+
} catch {}
|
|
6351
|
+
}
|
|
6352
|
+
function errorMessage3(error) {
|
|
6353
|
+
return error instanceof Error ? error.message : String(error);
|
|
6354
|
+
}
|
|
6355
|
+
function sessionIdOf2(session) {
|
|
6356
|
+
const id = session?.id;
|
|
6357
|
+
return typeof id === "string" && id !== "" ? id : undefined;
|
|
6358
|
+
}
|
|
6359
|
+
var watermarkCache = new Map;
|
|
6360
|
+
function loadWatermark(harnessDir) {
|
|
6361
|
+
const cached = watermarkCache.get(harnessDir);
|
|
6362
|
+
if (cached !== undefined)
|
|
6363
|
+
return cached;
|
|
6364
|
+
const watermark = new Map;
|
|
6365
|
+
try {
|
|
6366
|
+
const raw = readFileSync13(join17(harnessDir, WORKFLOW_LEDGER_WATERMARK_FILE), "utf8");
|
|
6367
|
+
const record = asRecord(JSON.parse(raw));
|
|
6368
|
+
const cursors = asRecord(record?.cursors);
|
|
6369
|
+
if (cursors !== undefined) {
|
|
6370
|
+
for (const [sid, next] of Object.entries(cursors)) {
|
|
6371
|
+
if (typeof next === "number" && Number.isInteger(next) && next >= 1 && next < WORKFLOW_LEDGER_MAX_SEQ && sid !== "") {
|
|
6372
|
+
watermark.set(sid, next);
|
|
6373
|
+
}
|
|
6374
|
+
}
|
|
6375
|
+
}
|
|
6376
|
+
} catch (error) {
|
|
6377
|
+
const err = error;
|
|
6378
|
+
if (err?.code !== "ENOENT") {
|
|
6379
|
+
log3("warn", `workflow-ledger watermark unreadable for ${harnessDir} — degrading to in-memory-only (restart re-records): ${errorMessage3(error)}`);
|
|
6380
|
+
}
|
|
6381
|
+
}
|
|
6382
|
+
watermarkCache.set(harnessDir, watermark);
|
|
6383
|
+
return watermark;
|
|
6384
|
+
}
|
|
6385
|
+
function saveWatermark(harnessDir, watermark) {
|
|
6386
|
+
try {
|
|
6387
|
+
const file = join17(harnessDir, WORKFLOW_LEDGER_WATERMARK_FILE);
|
|
6388
|
+
const tmp = `${file}.tmp`;
|
|
6389
|
+
writeFileSync4(tmp, JSON.stringify({ v: 1, cursors: Object.fromEntries(watermark) }));
|
|
6390
|
+
renameSync3(tmp, file);
|
|
6391
|
+
} catch (error) {
|
|
6392
|
+
log3("warn", `workflow-ledger watermark write failed for ${harnessDir} — in-memory only (restart re-records): ${errorMessage3(error)}`);
|
|
6393
|
+
}
|
|
6394
|
+
}
|
|
6395
|
+
function advanceWatermark(harnessDir, sid, nextSeq, isEvictable) {
|
|
6396
|
+
const watermark = loadWatermark(harnessDir);
|
|
6397
|
+
if (!watermark.has(sid) && watermark.size >= WORKFLOW_LEDGER_WATERMARK_MAX_SESSIONS) {
|
|
6398
|
+
let victim;
|
|
6399
|
+
for (const key of watermark.keys()) {
|
|
6400
|
+
if (isEvictable(key)) {
|
|
6401
|
+
victim = key;
|
|
6402
|
+
break;
|
|
6403
|
+
}
|
|
6404
|
+
}
|
|
6405
|
+
victim ??= watermark.keys().next().value;
|
|
6406
|
+
if (victim !== undefined) {
|
|
6407
|
+
watermark.delete(victim);
|
|
6408
|
+
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)`);
|
|
6409
|
+
}
|
|
6410
|
+
}
|
|
6411
|
+
watermark.set(sid, nextSeq);
|
|
6412
|
+
saveWatermark(harnessDir, watermark);
|
|
6413
|
+
}
|
|
6414
|
+
function rowOf(session, envelope) {
|
|
6415
|
+
const record = asRecord(envelope);
|
|
6416
|
+
if (record === undefined)
|
|
6417
|
+
return;
|
|
6418
|
+
const type = record.type;
|
|
6419
|
+
if (typeof type !== "string")
|
|
6420
|
+
return;
|
|
6421
|
+
const seq = record.seq;
|
|
6422
|
+
if (typeof seq !== "number" || !Number.isInteger(seq) || seq < 0 || seq >= WORKFLOW_LEDGER_MAX_SEQ)
|
|
6423
|
+
return;
|
|
6424
|
+
const time = record.time;
|
|
6425
|
+
if (typeof time !== "number" || !Number.isFinite(time))
|
|
6426
|
+
return;
|
|
6427
|
+
const data = asRecord(record.data);
|
|
6428
|
+
if (data === undefined)
|
|
6429
|
+
return;
|
|
6430
|
+
const runId = data.runId;
|
|
6431
|
+
if (typeof runId !== "string" || runId === "" || runId.length > WORKFLOW_LEDGER_MAX_ID_LENGTH)
|
|
6432
|
+
return;
|
|
6433
|
+
if (type === TOOL_WORKFLOW_RUN_START) {
|
|
6434
|
+
const name = typeof data.name === "string" ? normalizeWorkflowName(data.name) : data.name;
|
|
6435
|
+
if (typeof name !== "string" || name === "")
|
|
6436
|
+
return;
|
|
6437
|
+
const agent = sessionIdOf2(session);
|
|
6438
|
+
return {
|
|
6439
|
+
seq,
|
|
6440
|
+
runName: name,
|
|
6441
|
+
event: {
|
|
6442
|
+
v: 1,
|
|
6443
|
+
ts: time,
|
|
6444
|
+
kind: "workflow-run",
|
|
6445
|
+
runId,
|
|
6446
|
+
name: truncateLedgerField(name, WORKFLOW_LEDGER_MAX_NAME_LENGTH),
|
|
6447
|
+
...agent !== undefined ? { agent } : {}
|
|
6448
|
+
}
|
|
6449
|
+
};
|
|
6450
|
+
}
|
|
6451
|
+
if (type === TOOL_WORKFLOW_AGENT_START) {
|
|
6452
|
+
if (typeof data.seq !== "number" || !Number.isInteger(data.seq) || data.seq < 1 || data.seq >= WORKFLOW_LEDGER_MAX_SEQ) {
|
|
6453
|
+
return;
|
|
6454
|
+
}
|
|
6455
|
+
const label = typeof data.label === "string" ? normalizeWorkflowName(data.label) : data.label;
|
|
6456
|
+
if (typeof label !== "string" || label === "")
|
|
6457
|
+
return;
|
|
6458
|
+
if (typeof data.childId !== "string" || data.childId === "" || data.childId.length > WORKFLOW_LEDGER_MAX_ID_LENGTH)
|
|
6459
|
+
return;
|
|
6460
|
+
return {
|
|
6461
|
+
seq,
|
|
6462
|
+
childId: data.childId,
|
|
6463
|
+
event: {
|
|
6464
|
+
v: 1,
|
|
6465
|
+
ts: time,
|
|
6466
|
+
kind: "workflow-agent",
|
|
6467
|
+
runId,
|
|
6468
|
+
seq: data.seq,
|
|
6469
|
+
label: truncateLedgerField(label, WORKFLOW_LEDGER_MAX_LABEL_LENGTH),
|
|
6470
|
+
...typeof data.phase === "string" ? { phase: truncateLedgerField(normalizeWorkflowName(data.phase), WORKFLOW_LEDGER_MAX_LABEL_LENGTH) } : {},
|
|
6471
|
+
childId: data.childId
|
|
6472
|
+
}
|
|
6473
|
+
};
|
|
6474
|
+
}
|
|
6475
|
+
if (type === TOOL_WORKFLOW_AGENT_END) {
|
|
6476
|
+
return;
|
|
6477
|
+
}
|
|
6478
|
+
if (type === TOOL_WORKFLOW_RUN_END) {
|
|
6479
|
+
const stopReason = data.stopReason;
|
|
6480
|
+
if (stopReason !== "completed" && stopReason !== "cancelled" && stopReason !== "error")
|
|
6481
|
+
return;
|
|
6482
|
+
return {
|
|
6483
|
+
seq,
|
|
6484
|
+
event: { v: 1, ts: time, kind: "workflow-run-end", runId, stopReason }
|
|
6485
|
+
};
|
|
6486
|
+
}
|
|
6487
|
+
return;
|
|
6488
|
+
}
|
|
6489
|
+
function depthAdvisory(sessions, row, warned) {
|
|
6490
|
+
if (row.event.kind !== "workflow-agent" || row.childId === undefined)
|
|
6491
|
+
return;
|
|
6492
|
+
if (warned.has(row.event.runId))
|
|
6493
|
+
return;
|
|
6494
|
+
const child = sessions.get(row.childId);
|
|
6495
|
+
const delegationDepth = child?.header?.delegationDepth;
|
|
6496
|
+
if (typeof delegationDepth !== "number" || !Number.isFinite(delegationDepth) || delegationDepth < DEPTH_ADVISORY_THRESHOLD) {
|
|
6497
|
+
return;
|
|
6498
|
+
}
|
|
6499
|
+
warned.add(row.event.runId);
|
|
6500
|
+
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`);
|
|
6501
|
+
}
|
|
6502
|
+
function registerWorkflowLedger(ctx, resolver, workflowAskCache) {
|
|
6503
|
+
const sessions = ctx.get("sessions");
|
|
6504
|
+
if (sessions === undefined) {
|
|
6505
|
+
log3("debug", "sessions service absent — workflow-ledger consumer disabled (composition without dsh-session)");
|
|
6506
|
+
return;
|
|
6507
|
+
}
|
|
6508
|
+
const depthWarned = new Set;
|
|
6509
|
+
const consume = (session, envelope) => {
|
|
6510
|
+
const sid = sessionIdOf2(session);
|
|
6511
|
+
if (sid === undefined)
|
|
6512
|
+
return;
|
|
6513
|
+
const row = rowOf(session, envelope);
|
|
6514
|
+
if (row === undefined)
|
|
6515
|
+
return;
|
|
6516
|
+
const cwd = session?.header?.cwd;
|
|
6517
|
+
const harnessDir = resolver.forWorkspace(typeof cwd === "string" && cwd.trim() !== "" ? cwd : undefined);
|
|
6518
|
+
if (harnessDir === null)
|
|
6519
|
+
return;
|
|
6520
|
+
const watermark = loadWatermark(harnessDir);
|
|
6521
|
+
let next = watermark.get(sid) ?? 0;
|
|
6522
|
+
const events = session.events;
|
|
6523
|
+
if (Array.isArray(events) && events.length < next)
|
|
6524
|
+
next = 0;
|
|
6525
|
+
if (row.seq < next)
|
|
6526
|
+
return;
|
|
6527
|
+
if (recordWorkflowEvent({ harnessDir, event: row.event })) {
|
|
6528
|
+
advanceWatermark(harnessDir, sid, row.seq + 1, (candidate) => sessions.get(candidate) === undefined);
|
|
6529
|
+
if (workflowAskCache !== undefined && row.event.kind === "workflow-run") {
|
|
6530
|
+
try {
|
|
6531
|
+
const runName = row.runName ?? row.event.name;
|
|
6532
|
+
if (workflowAskCache.wasAsked(runName)) {
|
|
6533
|
+
workflowAskCache.record(runName, "allow");
|
|
6534
|
+
}
|
|
6535
|
+
} catch (error) {
|
|
6536
|
+
log3("warn", `workflow P-c allow observation degraded (contained — the ledger row stays): ${errorMessage3(error)}`);
|
|
6537
|
+
}
|
|
6538
|
+
}
|
|
6539
|
+
}
|
|
6540
|
+
if (row.event.kind === "workflow-agent") {
|
|
6541
|
+
try {
|
|
6542
|
+
depthAdvisory(sessions, row, depthWarned);
|
|
6543
|
+
} catch (error) {
|
|
6544
|
+
log3("warn", `workflow depth advisory degraded (contained — the run proceeds): ${errorMessage3(error)}`);
|
|
6545
|
+
}
|
|
6546
|
+
}
|
|
6547
|
+
};
|
|
6548
|
+
const scanSession = (session) => {
|
|
6549
|
+
const sid = sessionIdOf2(session);
|
|
6550
|
+
if (sid === undefined)
|
|
6551
|
+
return;
|
|
6552
|
+
const events = session.events;
|
|
6553
|
+
if (!Array.isArray(events))
|
|
6554
|
+
return;
|
|
6555
|
+
for (const envelope of events)
|
|
6556
|
+
consume(session, envelope);
|
|
6557
|
+
};
|
|
6558
|
+
ctx.events.on("session/created", (session) => {
|
|
6559
|
+
try {
|
|
6560
|
+
scanSession(session);
|
|
6561
|
+
} catch (error) {
|
|
6562
|
+
log3("warn", `workflow-ledger session-created backfill failed (contained — the session publication proceeds): ${errorMessage3(error)}`);
|
|
6563
|
+
}
|
|
6564
|
+
});
|
|
6565
|
+
let sessionsSnapshot;
|
|
6566
|
+
try {
|
|
6567
|
+
sessionsSnapshot = sessions.list();
|
|
6568
|
+
} catch (error) {
|
|
6569
|
+
log3("warn", `workflow-ledger cold scan could not list sessions (contained — the live firehose and session-created backfill stay active): ${errorMessage3(error)}`);
|
|
6570
|
+
sessionsSnapshot = [];
|
|
6571
|
+
}
|
|
6572
|
+
for (const session of sessionsSnapshot) {
|
|
6573
|
+
const sid = sessionIdOf2(session);
|
|
6574
|
+
if (sid === undefined)
|
|
6575
|
+
continue;
|
|
6576
|
+
try {
|
|
6577
|
+
scanSession(session);
|
|
6578
|
+
} catch (error) {
|
|
6579
|
+
log3("warn", `workflow-ledger cold scan failed for session ${sid} (contained — other sessions unaffected): ${errorMessage3(error)}`);
|
|
6580
|
+
}
|
|
6581
|
+
}
|
|
6582
|
+
ctx.events.on("session/event", (session, envelope) => {
|
|
6583
|
+
try {
|
|
6584
|
+
consume(session, envelope);
|
|
6585
|
+
} catch (error) {
|
|
6586
|
+
log3("warn", `workflow-ledger live consume failed (contained — the workflow run proceeds): ${errorMessage3(error)}`);
|
|
6587
|
+
}
|
|
6588
|
+
});
|
|
6589
|
+
}
|
|
6590
|
+
|
|
6591
|
+
// src/gates/goal-bridge.ts
|
|
6592
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14, readdirSync as readdirSync8 } from "node:fs";
|
|
6593
|
+
import { join as join18 } from "node:path";
|
|
6594
|
+
var GOAL_BRIDGE_LOGGER = "mstar/goal-bridge";
|
|
6595
|
+
var DEFAULT_MAX_GOAL_ROUNDS = 256;
|
|
6596
|
+
var FLOW_SEQUENCE = "iteration-start → per-plan cycles → iteration-close → PR delivery → merge-ready";
|
|
6597
|
+
var GOAL_CHANGE_EVENT_TYPE = "goal/change";
|
|
6598
|
+
var GOAL_CHANGE_VERSION = 1;
|
|
6599
|
+
var GOAL_ADVISORY_OBJECTIVE_CAP = 512;
|
|
6600
|
+
var GOAL_ADVISORY_MESSAGE_CAP = 512;
|
|
6601
|
+
var GOAL_ADVISORY_CODE_CAP = 128;
|
|
6602
|
+
var goalBridgeLogSink = () => {};
|
|
6603
|
+
function setGoalBridgeLogger(sink) {
|
|
6604
|
+
const prior = goalBridgeLogSink;
|
|
6605
|
+
goalBridgeLogSink = sink;
|
|
6606
|
+
return prior;
|
|
6607
|
+
}
|
|
6608
|
+
function log4(level, message) {
|
|
6609
|
+
goalBridgeLogSink(level, message);
|
|
6610
|
+
}
|
|
6611
|
+
function errorMessage4(error) {
|
|
6612
|
+
return error instanceof Error ? error.message : String(error);
|
|
6613
|
+
}
|
|
6614
|
+
function isRootLikeAgent(agent) {
|
|
6615
|
+
const header = agent?.session?.header;
|
|
6616
|
+
return header?.parentSession === undefined;
|
|
6617
|
+
}
|
|
6618
|
+
function iterationGoalObjective(iterationId) {
|
|
6619
|
+
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.";
|
|
6620
|
+
}
|
|
6621
|
+
function steeringCompass(harnessDir) {
|
|
6622
|
+
const iterationsDir = resolveIterationDir(harnessDir);
|
|
6623
|
+
if (!existsSync16(iterationsDir))
|
|
6624
|
+
return;
|
|
6625
|
+
let entries;
|
|
6626
|
+
try {
|
|
6627
|
+
entries = readdirSync8(iterationsDir, { withFileTypes: true });
|
|
6628
|
+
} catch {
|
|
6629
|
+
return;
|
|
6630
|
+
}
|
|
6631
|
+
for (const entry of entries) {
|
|
6632
|
+
if (!entry.isDirectory())
|
|
6633
|
+
continue;
|
|
6634
|
+
const compassPath = join18(iterationsDir, entry.name, "delivery-compass.md");
|
|
6635
|
+
if (!existsSync16(compassPath))
|
|
6636
|
+
continue;
|
|
6637
|
+
let content;
|
|
6638
|
+
try {
|
|
6639
|
+
content = readFileSync14(compassPath, "utf8");
|
|
6640
|
+
} catch {
|
|
6641
|
+
continue;
|
|
6642
|
+
}
|
|
6643
|
+
const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
6644
|
+
if (frontmatter === null || !/^status[ \t]*:[ \t]*(?:active|locked)[ \t]*$/m.test(frontmatter[1]))
|
|
6645
|
+
continue;
|
|
6646
|
+
return { iterationId: entry.name };
|
|
6647
|
+
}
|
|
6648
|
+
return;
|
|
6649
|
+
}
|
|
6650
|
+
var GOAL_STALE_REVISION = "GOAL_STALE_REVISION";
|
|
6651
|
+
function goalErrorCode(error) {
|
|
6652
|
+
if (typeof error !== "object" || error === null)
|
|
6653
|
+
return;
|
|
6654
|
+
if (!("code" in error))
|
|
6655
|
+
return;
|
|
6656
|
+
const code = error.code;
|
|
6657
|
+
return typeof code === "string" ? code : undefined;
|
|
6658
|
+
}
|
|
6659
|
+
function resolveGoalRounds(configValue) {
|
|
6660
|
+
if (configValue === undefined)
|
|
6661
|
+
return DEFAULT_MAX_GOAL_ROUNDS;
|
|
6662
|
+
if (!Number.isSafeInteger(configValue) || configValue < 1) {
|
|
6663
|
+
log4("warn", `invalid maxGoalRounds config ${String(configValue)} (must be a positive safe integer) — falling back to the default ${DEFAULT_MAX_GOAL_ROUNDS}`);
|
|
6664
|
+
return DEFAULT_MAX_GOAL_ROUNDS;
|
|
6665
|
+
}
|
|
6666
|
+
return configValue;
|
|
6667
|
+
}
|
|
6668
|
+
function replaceDriftedGoal(goals, agent, current, objective, maxGoalRounds) {
|
|
6669
|
+
if (current.phase === "complete") {
|
|
6670
|
+
goals.create(agent, { objective, maxGoalRounds });
|
|
6671
|
+
return;
|
|
6672
|
+
}
|
|
6673
|
+
goals.complete(agent, { id: current.id, revision: current.revision });
|
|
6674
|
+
goals.create(agent, { objective, maxGoalRounds });
|
|
6675
|
+
}
|
|
6676
|
+
function mirrorIterationGoal(agent, input) {
|
|
6677
|
+
const { resolver, goals, maxGoalRounds } = input;
|
|
6678
|
+
if (goals === undefined)
|
|
6679
|
+
return false;
|
|
6680
|
+
if (!isRootLikeAgent(agent))
|
|
6681
|
+
return false;
|
|
6682
|
+
const harnessDir = resolver.forAgent(agent);
|
|
6683
|
+
if (harnessDir === null)
|
|
6684
|
+
return false;
|
|
6685
|
+
const compass = steeringCompass(harnessDir);
|
|
6686
|
+
if (compass === undefined)
|
|
6687
|
+
return false;
|
|
6688
|
+
const objective = iterationGoalObjective(compass.iterationId);
|
|
6689
|
+
try {
|
|
6690
|
+
const current = goals.get(agent);
|
|
6691
|
+
if (current === undefined) {
|
|
6692
|
+
goals.create(agent, { objective, maxGoalRounds });
|
|
6693
|
+
return true;
|
|
6694
|
+
}
|
|
6695
|
+
if (current.objective === objective)
|
|
6696
|
+
return true;
|
|
6697
|
+
try {
|
|
6698
|
+
replaceDriftedGoal(goals, agent, current, objective, maxGoalRounds);
|
|
6699
|
+
return true;
|
|
6700
|
+
} catch (error) {
|
|
6701
|
+
if (goalErrorCode(error) !== GOAL_STALE_REVISION)
|
|
6702
|
+
throw error;
|
|
6703
|
+
const fresh = goals.get(agent);
|
|
6704
|
+
if (fresh === undefined) {
|
|
6705
|
+
goals.create(agent, { objective, maxGoalRounds });
|
|
6706
|
+
return true;
|
|
6707
|
+
}
|
|
6708
|
+
if (fresh.objective === objective)
|
|
6709
|
+
return true;
|
|
6710
|
+
replaceDriftedGoal(goals, agent, fresh, objective, maxGoalRounds);
|
|
6711
|
+
return true;
|
|
6712
|
+
}
|
|
6713
|
+
} catch (error) {
|
|
6714
|
+
const code = goalErrorCode(error);
|
|
6715
|
+
log4("warn", `goal mirror failed (contained — the session/decision point proceeds)${code !== undefined ? ` [${code}]` : ""}: ${errorMessage4(error)}`);
|
|
6716
|
+
return false;
|
|
6717
|
+
}
|
|
6718
|
+
}
|
|
6719
|
+
function blockedAdvisoryOf(envelope) {
|
|
6720
|
+
const record = asRecord(envelope);
|
|
6721
|
+
if (record === undefined || record.type !== GOAL_CHANGE_EVENT_TYPE)
|
|
6722
|
+
return;
|
|
6723
|
+
const data = asRecord(record.data);
|
|
6724
|
+
if (data === undefined)
|
|
6725
|
+
return;
|
|
6726
|
+
if (data.version !== GOAL_CHANGE_VERSION)
|
|
6727
|
+
return;
|
|
6728
|
+
const goal = asRecord(data.goal);
|
|
6729
|
+
if (goal === undefined)
|
|
6730
|
+
return;
|
|
6731
|
+
if (data.operation !== "block" && goal.phase !== "blocked")
|
|
6732
|
+
return;
|
|
6733
|
+
const blockedReason = asRecord(goal.blockedReason);
|
|
6734
|
+
if (blockedReason === undefined)
|
|
6735
|
+
return;
|
|
6736
|
+
const code = blockedReason.code;
|
|
6737
|
+
const message = blockedReason.message;
|
|
6738
|
+
const objective = goal.objective;
|
|
6739
|
+
if (typeof code !== "string" || code === "" || typeof message !== "string" || message === "" || typeof objective !== "string" || objective === "") {
|
|
6740
|
+
return;
|
|
6741
|
+
}
|
|
6742
|
+
return { code, message, objective };
|
|
6743
|
+
}
|
|
6744
|
+
function warnBlockedGoal(harnessDir, advisory) {
|
|
6745
|
+
const code = truncateLedgerField(advisory.code, GOAL_ADVISORY_CODE_CAP);
|
|
6746
|
+
const reason = truncateLedgerField(normalizeWorkflowName(advisory.message), GOAL_ADVISORY_MESSAGE_CAP);
|
|
6747
|
+
const objective = truncateLedgerField(normalizeWorkflowName(advisory.objective), GOAL_ADVISORY_OBJECTIVE_CAP);
|
|
6748
|
+
log4("warn", `goal blocked [${code}] — ${reason}; objective: ${objective}; residuals: see ${harnessDir}/${STATUS_FILE} (residual_findings) — advisory only, zero harness writes`);
|
|
6749
|
+
}
|
|
6750
|
+
function rootAgentOf(agent, agents) {
|
|
6751
|
+
let current = agent;
|
|
6752
|
+
const seen = new Set;
|
|
6753
|
+
for (;; ) {
|
|
6754
|
+
const header = current?.session?.header;
|
|
6755
|
+
if (header === undefined)
|
|
6756
|
+
return;
|
|
6757
|
+
if (header.parentSession === undefined)
|
|
6758
|
+
return current;
|
|
6759
|
+
if (typeof header.parentSession !== "string")
|
|
6760
|
+
return;
|
|
6761
|
+
if (seen.has(header.parentSession))
|
|
6762
|
+
return;
|
|
6763
|
+
seen.add(header.parentSession);
|
|
6764
|
+
const parent = agents.get(header.parentSession);
|
|
6765
|
+
if (parent === undefined)
|
|
6766
|
+
return;
|
|
6767
|
+
current = parent;
|
|
6768
|
+
}
|
|
6769
|
+
}
|
|
6770
|
+
function registerGoalBridge(ctx, resolver, config) {
|
|
6771
|
+
const goals = ctx.get("goals");
|
|
6772
|
+
if (goals === undefined) {
|
|
6773
|
+
log4("debug", "goals service absent — goal bridge disabled (composition without @deepseek-ai/dsh-goal)");
|
|
6774
|
+
}
|
|
6775
|
+
const maxGoalRounds = resolveGoalRounds(config.maxGoalRounds);
|
|
6776
|
+
const mirror = (agent) => {
|
|
6777
|
+
try {
|
|
6778
|
+
mirrorIterationGoal(agent, { resolver, goals, maxGoalRounds });
|
|
6779
|
+
} catch (error) {
|
|
6780
|
+
log4("warn", `goal bridge mirror failed (contained — the session/decision point proceeds): ${errorMessage4(error)}`);
|
|
6781
|
+
}
|
|
6782
|
+
};
|
|
6783
|
+
ctx.events.on("agent/session-start", (payload) => {
|
|
6784
|
+
if (payload.agent !== undefined)
|
|
6785
|
+
mirror(payload.agent);
|
|
6786
|
+
});
|
|
6787
|
+
ctx.events.on("subagent/start", (info) => {
|
|
6788
|
+
const agents = ctx.get("agents");
|
|
6789
|
+
if (agents === undefined || typeof info.id !== "string")
|
|
6790
|
+
return;
|
|
6791
|
+
const child = agents.get(info.id);
|
|
6792
|
+
if (child === undefined)
|
|
6793
|
+
return;
|
|
6794
|
+
const root = rootAgentOf(child, agents);
|
|
6795
|
+
if (root !== undefined)
|
|
6796
|
+
mirror(root);
|
|
6797
|
+
});
|
|
6798
|
+
ctx.events.on("session/event", (session, envelope) => {
|
|
6799
|
+
try {
|
|
6800
|
+
const advisory = blockedAdvisoryOf(envelope);
|
|
6801
|
+
if (advisory === undefined)
|
|
6802
|
+
return;
|
|
6803
|
+
const cwd = session?.header?.cwd;
|
|
6804
|
+
const harnessDir = resolver.forWorkspace(typeof cwd === "string" && cwd.trim() !== "" ? cwd : undefined);
|
|
6805
|
+
if (harnessDir === null)
|
|
6806
|
+
return;
|
|
6807
|
+
warnBlockedGoal(harnessDir, advisory);
|
|
6808
|
+
} catch (error) {
|
|
6809
|
+
log4("warn", `goal blocked advisory degraded (contained — the session proceeds): ${errorMessage4(error)}`);
|
|
6810
|
+
}
|
|
6811
|
+
});
|
|
6812
|
+
}
|
|
6813
|
+
|
|
6814
|
+
// src/gates/plan-mode-bridge.ts
|
|
6815
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
6816
|
+
import { join as join19 } from "node:path";
|
|
6817
|
+
var PLAN_MODE_BRIDGE_LOGGER = "mstar/plan-mode-bridge";
|
|
6818
|
+
var PLAN_STATUS_TODO = "Todo";
|
|
6819
|
+
var planModeBridgeLogSink = () => {};
|
|
6820
|
+
function setPlanModeBridgeLogger(sink) {
|
|
6821
|
+
const prior = planModeBridgeLogSink;
|
|
6822
|
+
planModeBridgeLogSink = sink;
|
|
6823
|
+
return prior;
|
|
6824
|
+
}
|
|
6825
|
+
function log5(level, message) {
|
|
6826
|
+
planModeBridgeLogSink(level, message);
|
|
6827
|
+
}
|
|
6828
|
+
function errorMessage5(error) {
|
|
6829
|
+
return error instanceof Error ? error.message : String(error);
|
|
6830
|
+
}
|
|
6831
|
+
function hasPrepareWindow(harnessDir) {
|
|
6832
|
+
const statusPath = join19(harnessDir, STATUS_FILE);
|
|
6833
|
+
if (!existsSync17(statusPath))
|
|
6834
|
+
return false;
|
|
6835
|
+
let doc;
|
|
6836
|
+
try {
|
|
6837
|
+
doc = readJson(statusPath);
|
|
6838
|
+
} catch {
|
|
6839
|
+
return false;
|
|
6840
|
+
}
|
|
6841
|
+
const plans = doc.plans;
|
|
6842
|
+
if (!Array.isArray(plans))
|
|
6843
|
+
return false;
|
|
6844
|
+
return plans.some((row) => asRecord(row)?.status === PLAN_STATUS_TODO);
|
|
6845
|
+
}
|
|
6846
|
+
function planModeTarget(harnessDir) {
|
|
6847
|
+
if (steeringCompass(harnessDir) === undefined)
|
|
6848
|
+
return false;
|
|
6849
|
+
return hasPrepareWindow(harnessDir);
|
|
6850
|
+
}
|
|
6851
|
+
function syncPlanMode(agent, input) {
|
|
6852
|
+
const { resolver, planMode } = input;
|
|
6853
|
+
if (planMode === undefined)
|
|
6854
|
+
return false;
|
|
6855
|
+
if (!isRootLikeAgent(agent))
|
|
6856
|
+
return false;
|
|
6857
|
+
const harnessDir = resolver.forAgent(agent);
|
|
6858
|
+
if (harnessDir === null)
|
|
6859
|
+
return false;
|
|
6860
|
+
try {
|
|
6861
|
+
planMode.set(agent, planModeTarget(harnessDir));
|
|
6862
|
+
return true;
|
|
6863
|
+
} catch (error) {
|
|
6864
|
+
log5("warn", `planMode sync failed (contained — the session/decision point proceeds): ${errorMessage5(error)}`);
|
|
6865
|
+
return false;
|
|
6866
|
+
}
|
|
6867
|
+
}
|
|
6868
|
+
function registerPlanModeBridge(ctx, resolver) {
|
|
6869
|
+
const planMode = ctx.get("planMode");
|
|
6870
|
+
if (planMode === undefined) {
|
|
6871
|
+
log5("debug", "planMode service absent — plan-mode bridge disabled (composition without @deepseek-ai/dsh-plan-mode)");
|
|
6872
|
+
}
|
|
6873
|
+
const sync = (agent) => {
|
|
6874
|
+
try {
|
|
6875
|
+
syncPlanMode(agent, { resolver, planMode });
|
|
6876
|
+
} catch (error) {
|
|
6877
|
+
log5("warn", `planMode bridge sync failed (contained — the session/decision point proceeds): ${errorMessage5(error)}`);
|
|
6878
|
+
}
|
|
6879
|
+
};
|
|
6880
|
+
ctx.events.on("agent/session-start", (payload) => {
|
|
6881
|
+
if (payload.agent !== undefined)
|
|
6882
|
+
sync(payload.agent);
|
|
6883
|
+
});
|
|
6884
|
+
ctx.events.on("subagent/start", (info) => {
|
|
6885
|
+
const agents = ctx.get("agents");
|
|
6886
|
+
if (agents === undefined || typeof info.id !== "string")
|
|
6887
|
+
return;
|
|
6888
|
+
const child = agents.get(info.id);
|
|
6889
|
+
if (child === undefined)
|
|
6890
|
+
return;
|
|
6891
|
+
const root = rootAgentOf(child, agents);
|
|
6892
|
+
if (root !== undefined)
|
|
6893
|
+
sync(root);
|
|
6894
|
+
});
|
|
6895
|
+
}
|
|
6896
|
+
|
|
6897
|
+
// src/gates/fallbacks-seeds.ts
|
|
6898
|
+
var SEEDS_LOGGER = "mstar/fallbacks-seeds";
|
|
6899
|
+
var EMPTY_OUTCOME = { applied: [], skipped: [], conflicts: [] };
|
|
6900
|
+
function errorMessage6(error) {
|
|
6901
|
+
return error instanceof Error ? error.message : String(error);
|
|
6902
|
+
}
|
|
6903
|
+
function mstarLoadLine(roleId) {
|
|
6904
|
+
return `Load mstar-roles (references/${roleId}.md) and the role's Required Skill Dependencies before acting.`;
|
|
6905
|
+
}
|
|
6906
|
+
async function declareMstarSeeds(service, options) {
|
|
6907
|
+
const { agentsDir, log: log6 } = options;
|
|
6908
|
+
let readback;
|
|
6909
|
+
try {
|
|
6910
|
+
readback = service.getEffectiveRoles();
|
|
6911
|
+
} catch (error) {
|
|
6912
|
+
log6("warn", `fallbacks seed readback failed — mstar seeds not declared: ${errorMessage6(error)}`);
|
|
6913
|
+
return { declared: [], skipped: [], preserved: [], outcome: EMPTY_OUTCOME };
|
|
6914
|
+
}
|
|
6915
|
+
const mstarIds = agentsDir === undefined ? [] : subagentRoleIds(agentsDir);
|
|
6916
|
+
const mstarSet = new Set(mstarIds);
|
|
6917
|
+
const preservedCandidates = [];
|
|
6918
|
+
for (const row of readback.roles) {
|
|
6919
|
+
if (!row.seeded)
|
|
6920
|
+
continue;
|
|
6921
|
+
const id = row.id.trim();
|
|
6922
|
+
if (id === "" || mstarSet.has(id))
|
|
6923
|
+
continue;
|
|
6924
|
+
if (row.seedPersona === undefined || row.seedPersona.trim() === "") {
|
|
6925
|
+
log6("debug", `seeded row '${id}' carries no seedPersona (upstream contract: present iff seeded) — not preserved`);
|
|
6926
|
+
continue;
|
|
6927
|
+
}
|
|
6928
|
+
preservedCandidates.push({ id, persona: row.seedPersona });
|
|
6929
|
+
}
|
|
6930
|
+
const declared = [];
|
|
6931
|
+
const skipped = [];
|
|
6932
|
+
const forwardWarn = (message) => log6("warn", message);
|
|
6933
|
+
for (const roleId of mstarIds) {
|
|
6934
|
+
const persona = personaFor(roleId, { agentsDir }, forwardWarn);
|
|
6935
|
+
if (persona === undefined) {
|
|
6936
|
+
skipped.push({ id: roleId, reason: "no-persona" });
|
|
6937
|
+
continue;
|
|
6938
|
+
}
|
|
6939
|
+
if (PERSONA_INTERPOLATION_HAZARD.test(persona.text)) {
|
|
6940
|
+
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`);
|
|
6941
|
+
skipped.push({ id: roleId, reason: "interpolation" });
|
|
6942
|
+
continue;
|
|
6943
|
+
}
|
|
6944
|
+
declared.push({ id: roleId, persona: `${persona.text}
|
|
6945
|
+
|
|
6946
|
+
${mstarLoadLine(roleId)}` });
|
|
6947
|
+
}
|
|
6948
|
+
const preserved = [];
|
|
6949
|
+
for (const entry of preservedCandidates) {
|
|
6950
|
+
if (PERSONA_INTERPOLATION_HAZARD.test(entry.persona)) {
|
|
6951
|
+
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`);
|
|
6952
|
+
skipped.push({ id: entry.id, reason: "interpolation" });
|
|
6953
|
+
continue;
|
|
6954
|
+
}
|
|
6955
|
+
declared.push({ id: entry.id, persona: entry.persona });
|
|
6956
|
+
preserved.push(entry);
|
|
6957
|
+
}
|
|
6958
|
+
if (declared.length === 0) {
|
|
6959
|
+
log6("debug", "no mstar or preserved seeds to declare — empty batch skipped (registry untouched)");
|
|
6960
|
+
return { declared, skipped, preserved, outcome: EMPTY_OUTCOME };
|
|
6961
|
+
}
|
|
6962
|
+
const outcome = await service.declareSeeds(declared);
|
|
6963
|
+
log6("debug", `mstar seeds declared: ${declared.length} ids (${mstarIds.length} mstar + ${preserved.length} preserved); ${skipped.length} skipped locally`);
|
|
6964
|
+
return { declared, skipped, preserved, outcome };
|
|
6965
|
+
}
|
|
6966
|
+
|
|
6967
|
+
// src/gates/fallbacks-advisory.ts
|
|
6968
|
+
var ADVISORY_LOGGER = "mstar/fallbacks-advisory";
|
|
6969
|
+
var advisoryLogSink = () => {};
|
|
6970
|
+
function setAdvisoryLogger(sink) {
|
|
6971
|
+
const prior = advisoryLogSink;
|
|
6972
|
+
advisoryLogSink = sink;
|
|
6973
|
+
return prior;
|
|
6974
|
+
}
|
|
6975
|
+
var ADVISORY_ID_LIST_CAP = 20;
|
|
6976
|
+
function capIdList(items) {
|
|
6977
|
+
if (items.length <= ADVISORY_ID_LIST_CAP)
|
|
6978
|
+
return items.join(", ");
|
|
6979
|
+
return `${items.slice(0, ADVISORY_ID_LIST_CAP).join(", ")}… and ${items.length - ADVISORY_ID_LIST_CAP} more`;
|
|
6980
|
+
}
|
|
6981
|
+
async function runFallbacksAdvisory(ctx, agentsDir) {
|
|
6982
|
+
try {
|
|
6983
|
+
if (!fallbacksMounted(ctx))
|
|
6984
|
+
return false;
|
|
6985
|
+
const config = readRowConfig(ctx);
|
|
6986
|
+
if (config === undefined) {
|
|
6987
|
+
log6("debug", "fallbacks row config unreadable (absent or non-object) — adoption advisory skipped");
|
|
6988
|
+
return true;
|
|
6989
|
+
}
|
|
6990
|
+
const service = fallbacksService(ctx);
|
|
6991
|
+
if (service !== undefined) {
|
|
6992
|
+
return await runSeedsAdvisory(service, config, agentsDir);
|
|
6993
|
+
}
|
|
6994
|
+
return runStructuralAdvisory(config, agentsDir);
|
|
6995
|
+
} catch (error) {
|
|
6996
|
+
log6("warn", `fallbacks adoption advisory aborted (degraded — warn-only, deployment config untouched): ${errorMessage7(error)}`);
|
|
6997
|
+
return true;
|
|
6998
|
+
}
|
|
6999
|
+
}
|
|
7000
|
+
async function runSeedsAdvisory(service, config, agentsDir) {
|
|
7001
|
+
const legacy = service.detectLegacyKeys(config);
|
|
7002
|
+
if (legacy.length > 0) {
|
|
7003
|
+
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)`);
|
|
7004
|
+
}
|
|
7005
|
+
if (agentsDir === undefined) {
|
|
7006
|
+
log6("debug", "harness-agents mirror absent — fallbacks adoption taxonomy check skipped (config-only advisory)");
|
|
7007
|
+
return true;
|
|
7008
|
+
}
|
|
7009
|
+
const mstarIds = subagentRoleIds(agentsDir);
|
|
7010
|
+
if (mstarIds.length === 0) {
|
|
7011
|
+
log6("debug", "harness-agents mirror has no subagent-mode shells — fallbacks adoption taxonomy check skipped");
|
|
7012
|
+
return true;
|
|
7013
|
+
}
|
|
7014
|
+
const seedsLog = (_level, message) => log6("debug", message);
|
|
7015
|
+
const view = await declareMstarSeeds(service, { agentsDir, log: seedsLog });
|
|
7016
|
+
let readback;
|
|
7017
|
+
try {
|
|
7018
|
+
readback = service.getEffectiveRoles();
|
|
7019
|
+
} catch (error) {
|
|
7020
|
+
log6("warn", `fallbacks effective-state readback failed — adoption state unavailable: ${errorMessage7(error)}`);
|
|
7021
|
+
return true;
|
|
7022
|
+
}
|
|
7023
|
+
reportEffectiveState(mstarIds, readback);
|
|
7024
|
+
reportDeclareOutcome(view);
|
|
7025
|
+
return true;
|
|
7026
|
+
}
|
|
7027
|
+
function runStructuralAdvisory(config, agentsDir) {
|
|
7028
|
+
if (agentsDir === undefined) {
|
|
7029
|
+
log6("debug", "harness-agents mirror absent — fallbacks adoption taxonomy check skipped (config-only advisory)");
|
|
7030
|
+
return true;
|
|
7031
|
+
}
|
|
7032
|
+
const mstarIds = subagentRoleIds(agentsDir);
|
|
7033
|
+
if (mstarIds.length === 0) {
|
|
7034
|
+
log6("debug", "harness-agents mirror has no subagent-mode shells — fallbacks adoption taxonomy check skipped");
|
|
7035
|
+
return true;
|
|
7036
|
+
}
|
|
7037
|
+
const list = readRolesList(config);
|
|
7038
|
+
if (list === undefined) {
|
|
7039
|
+
log6("debug", "fallbacks roles block not structurally readable (roles.list absent or non-array) — adoption advisory skipped");
|
|
7040
|
+
return true;
|
|
7041
|
+
}
|
|
7042
|
+
const declared = new Map;
|
|
7043
|
+
for (let index = 0;index < list.length; index++) {
|
|
7044
|
+
const entity = list[index];
|
|
7045
|
+
if (entity === null || typeof entity !== "object") {
|
|
7046
|
+
log6("debug", `fallbacks roles.list entry ${index} is not an object (${entity === null ? "null" : typeof entity}) — skipped`);
|
|
7047
|
+
continue;
|
|
7048
|
+
}
|
|
7049
|
+
const view = entity;
|
|
7050
|
+
if (typeof view.id !== "string" || view.id === "")
|
|
7051
|
+
continue;
|
|
7052
|
+
declared.set(view.id, view);
|
|
7053
|
+
}
|
|
7054
|
+
const missing = mstarIds.filter((id) => !declared.has(id));
|
|
7055
|
+
if (missing.length > 0) {
|
|
7056
|
+
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`);
|
|
7057
|
+
}
|
|
7058
|
+
const emptyPersona = [...declared.values()].filter((entity) => {
|
|
7059
|
+
const persona = entity.persona;
|
|
7060
|
+
return typeof persona !== "string" || persona.trim() === "";
|
|
7061
|
+
}).map((entity) => entity.id);
|
|
7062
|
+
if (emptyPersona.length > 0) {
|
|
7063
|
+
log6("warn", `fallbacks roles.list declares roles with an empty persona: ${capIdList(emptyPersona)} — declare a persona (or remove the role)`);
|
|
7064
|
+
}
|
|
7065
|
+
return true;
|
|
7066
|
+
}
|
|
7067
|
+
function reportEffectiveState(mstarIds, readback) {
|
|
7068
|
+
const byId = new Map;
|
|
7069
|
+
for (const row of readback.roles) {
|
|
7070
|
+
const key = row.id.trim();
|
|
7071
|
+
if (byId.has(key)) {
|
|
7072
|
+
log6("debug", `effective readback carries a duplicate id '${key}' after trimming — first row wins for the three-state report`);
|
|
7073
|
+
continue;
|
|
7074
|
+
}
|
|
7075
|
+
byId.set(key, row);
|
|
7076
|
+
}
|
|
7077
|
+
const missing = [];
|
|
7078
|
+
const overridden = [];
|
|
7079
|
+
const seeded = [];
|
|
7080
|
+
for (const id of mstarIds) {
|
|
7081
|
+
const row = byId.get(id);
|
|
7082
|
+
if (row === undefined) {
|
|
7083
|
+
missing.push(id);
|
|
7084
|
+
continue;
|
|
7085
|
+
}
|
|
7086
|
+
if (row.personaOverridden) {
|
|
7087
|
+
overridden.push(id);
|
|
7088
|
+
continue;
|
|
7089
|
+
}
|
|
7090
|
+
if (row.seeded)
|
|
7091
|
+
seeded.push(id);
|
|
7092
|
+
}
|
|
7093
|
+
if (missing.length > 0) {
|
|
7094
|
+
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`);
|
|
7095
|
+
}
|
|
7096
|
+
if (seeded.length > 0) {
|
|
7097
|
+
log6("debug", `fallbacks taxonomy: mstar roles seeded at their defaults — ${capIdList(seeded)}`);
|
|
7098
|
+
}
|
|
7099
|
+
if (overridden.length > 0) {
|
|
7100
|
+
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)`);
|
|
7101
|
+
}
|
|
7102
|
+
const emptyPersona = readback.roles.filter((row) => {
|
|
7103
|
+
const persona = row.persona;
|
|
7104
|
+
if (typeof persona === "string" && persona.trim() !== "")
|
|
7105
|
+
return false;
|
|
7106
|
+
return !row.seeded || row.personaOverridden;
|
|
7107
|
+
}).map((row) => row.id);
|
|
7108
|
+
if (emptyPersona.length > 0) {
|
|
7109
|
+
log6("warn", `fallbacks taxonomy declares roles with an empty persona: ${capIdList(emptyPersona)} — declare a persona (or remove the role)`);
|
|
7110
|
+
}
|
|
7111
|
+
}
|
|
7112
|
+
function reportDeclareOutcome(view) {
|
|
7113
|
+
const { skipped, outcome } = view;
|
|
7114
|
+
const conflicts = outcome.conflicts;
|
|
7115
|
+
if (skipped.length === 0 && outcome.skipped.length === 0 && conflicts.length === 0)
|
|
7116
|
+
return;
|
|
7117
|
+
const parts = [];
|
|
7118
|
+
if (skipped.length > 0) {
|
|
7119
|
+
parts.push(`skipped locally: ${capIdList(skipped.map((s) => `${s.id} (${s.reason})`))}`);
|
|
7120
|
+
}
|
|
7121
|
+
if (outcome.skipped.length > 0) {
|
|
7122
|
+
parts.push(`skipped upstream: ${capIdList(outcome.skipped.map((s) => `${s.id} (${s.reason})`))}`);
|
|
7123
|
+
}
|
|
7124
|
+
if (conflicts.length > 0) {
|
|
7125
|
+
parts.push(`conflicts: ${capIdList(conflicts.map((c) => `${c.id} (${c.kind} — operator override retained)`))}`);
|
|
7126
|
+
}
|
|
7127
|
+
const revertNote = conflicts.length > 0 ? " — revert an override via the fallbacks settings card rollback button (fallbacks/revert-seed gateway)" : "";
|
|
7128
|
+
log6("warn", `fallbacks seed declaration: ${parts.join("; ")}${revertNote}`);
|
|
7129
|
+
}
|
|
7130
|
+
function errorMessage7(error) {
|
|
7131
|
+
return error instanceof Error ? error.message : String(error);
|
|
7132
|
+
}
|
|
7133
|
+
function readRowConfig(ctx) {
|
|
7134
|
+
const entry = fallbacksEntry(ctx);
|
|
7135
|
+
if (entry === undefined)
|
|
7136
|
+
return;
|
|
7137
|
+
const config = entry.options.config;
|
|
7138
|
+
if (config === undefined || config === null || typeof config !== "object" || Array.isArray(config))
|
|
7139
|
+
return;
|
|
7140
|
+
return config;
|
|
7141
|
+
}
|
|
7142
|
+
function readRolesList(config) {
|
|
7143
|
+
const roles = config.roles;
|
|
7144
|
+
if (roles === undefined || roles === null || typeof roles !== "object" || Array.isArray(roles))
|
|
7145
|
+
return;
|
|
7146
|
+
const list = roles.list;
|
|
7147
|
+
if (!Array.isArray(list))
|
|
7148
|
+
return;
|
|
7149
|
+
return list;
|
|
7150
|
+
}
|
|
7151
|
+
function log6(level, message) {
|
|
7152
|
+
try {
|
|
7153
|
+
advisoryLogSink(level, message);
|
|
7154
|
+
} catch {}
|
|
7155
|
+
}
|
|
7156
|
+
|
|
7157
|
+
// src/gates/system-prompt.ts
|
|
7158
|
+
var HARNESS_PROMPT_LOGGER = "mstar/harness-prompt";
|
|
7159
|
+
var HARNESS_RULES_SECTION_NAME = "mstar:harness-rules";
|
|
7160
|
+
var HARNESS_RULES_SECTION_ORDER = 2;
|
|
7161
|
+
var ENGINE_STATUS_CONTEXT_NAME = "mstar:engine-status";
|
|
7162
|
+
var ENGINE_STATUS_CONTEXT_ORDER = 100;
|
|
7163
|
+
var harnessPromptLogSink = () => {};
|
|
7164
|
+
function setHarnessPromptLogger(sink) {
|
|
7165
|
+
const prior = harnessPromptLogSink;
|
|
7166
|
+
harnessPromptLogSink = sink;
|
|
7167
|
+
return prior;
|
|
7168
|
+
}
|
|
7169
|
+
function resolveAssemblyHarnessDir(context, resolver, fallback) {
|
|
7170
|
+
const agent = context?.agent;
|
|
7171
|
+
if (agent === undefined || agent === null)
|
|
7172
|
+
return fallback;
|
|
7173
|
+
try {
|
|
7174
|
+
return resolver.forAgent(agent);
|
|
7175
|
+
} catch (error) {
|
|
7176
|
+
log7("debug", `mstar:harness-rules per-assembly harness-dir resolution failed (falling back to the boot value): ${errorMessage8(error)}`);
|
|
7177
|
+
return fallback;
|
|
7178
|
+
}
|
|
7179
|
+
}
|
|
7180
|
+
function registerHarnessPrompt(ctx, options) {
|
|
7181
|
+
const systemPrompt = ctx.get("systemPrompt");
|
|
7182
|
+
if (systemPrompt === undefined || typeof systemPrompt.section !== "function" || typeof systemPrompt.context !== "function") {
|
|
7183
|
+
log7("debug", "systemPrompt service absent — mstar:harness-rules injection skipped (composition without dsh-system-prompt)");
|
|
7184
|
+
return false;
|
|
7185
|
+
}
|
|
7186
|
+
let bootHarnessDir;
|
|
7187
|
+
try {
|
|
7188
|
+
bootHarnessDir = options.resolver.forWorkspace(undefined);
|
|
7189
|
+
} catch (error) {
|
|
7190
|
+
log7("warn", `mstar:harness-rules injection aborted (degraded — session boot unaffected): ${errorMessage8(error)}`);
|
|
7191
|
+
return false;
|
|
7192
|
+
}
|
|
7193
|
+
ctx.inject(["systemPrompt"], (systemPromptCtx) => {
|
|
7194
|
+
try {
|
|
7195
|
+
const view = systemPromptCtx.systemPrompt;
|
|
7196
|
+
const disposeSection = view.section({
|
|
7197
|
+
name: HARNESS_RULES_SECTION_NAME,
|
|
7198
|
+
order: HARNESS_RULES_SECTION_ORDER,
|
|
7199
|
+
text: (context) => {
|
|
7200
|
+
const harnessDir = resolveAssemblyHarnessDir(context, options.resolver, bootHarnessDir);
|
|
7201
|
+
return harnessRulesText(harnessDir, sectionEnforcement(harnessDir));
|
|
7202
|
+
}
|
|
7203
|
+
});
|
|
7204
|
+
const disposeContext = view.context({
|
|
7205
|
+
name: ENGINE_STATUS_CONTEXT_NAME,
|
|
7206
|
+
order: ENGINE_STATUS_CONTEXT_ORDER,
|
|
7207
|
+
text: engineStatusProvider(ctx, options.resolver, bootHarnessDir)
|
|
7208
|
+
});
|
|
7209
|
+
systemPromptCtx.effect(function* () {
|
|
7210
|
+
yield () => {
|
|
7211
|
+
try {
|
|
7212
|
+
disposeSection();
|
|
7213
|
+
} catch {}
|
|
7214
|
+
try {
|
|
7215
|
+
disposeContext();
|
|
7216
|
+
} catch {}
|
|
7217
|
+
};
|
|
7218
|
+
}, "mstar:harness-rules disposer collection");
|
|
7219
|
+
} catch (error) {
|
|
7220
|
+
log7("warn", `mstar:harness-rules registration failed (contained — session boot unaffected): ${errorMessage8(error)}`);
|
|
7221
|
+
}
|
|
7222
|
+
});
|
|
7223
|
+
return true;
|
|
7224
|
+
}
|
|
7225
|
+
function sectionEnforcement(harnessDir) {
|
|
7226
|
+
return harnessDir === null ? { hard: false, source: "none" } : resolveCompassEnforcement(harnessDir);
|
|
7227
|
+
}
|
|
7228
|
+
function harnessRulesText(harnessDir, enforcement) {
|
|
7229
|
+
return [
|
|
7230
|
+
"This session runs under the Morning Star (mstar) harness: prompts and tool calls are governed by the harness gates and rules.",
|
|
7231
|
+
`enforcement: ${enforcement.hard ? "hard" : "soft"}`,
|
|
7232
|
+
`harness dir: ${stripInterpolationHazard(harnessDir ?? "none")}`,
|
|
7233
|
+
"Before non-trivial work, read the skill `skill://mstar-harness-core` — the authoritative harness entry point (mstar-harness-core first, then topic skills)."
|
|
7234
|
+
].join(`
|
|
7235
|
+
`);
|
|
7236
|
+
}
|
|
7237
|
+
function engineStatusProvider(ctx, resolver, bootHarnessDir) {
|
|
7238
|
+
const memo = new Map;
|
|
7239
|
+
return (context) => {
|
|
7240
|
+
const harnessDir = resolveAssemblyHarnessDir(context, resolver, bootHarnessDir);
|
|
7241
|
+
const now = Date.now();
|
|
7242
|
+
let entry = memo.get(harnessDir);
|
|
7243
|
+
if (entry === undefined || now - entry.builtAt >= DEFAULT_CATALOG_TTL_MS) {
|
|
7244
|
+
entry = { source: buildCatalogSources(ctx, harnessDir), builtAt: now };
|
|
7245
|
+
memo.set(harnessDir, entry);
|
|
7246
|
+
}
|
|
7247
|
+
return engineStatusSummary(entry.source);
|
|
7248
|
+
};
|
|
7249
|
+
}
|
|
7250
|
+
function engineStatusSummary(source) {
|
|
7251
|
+
const lines = [
|
|
7252
|
+
`mstar engine status: v${source.version} | harness ${stripInterpolationHazard(source.harnessDir ?? "none")} | enforcement ${source.enforcement.hard ? "hard" : "soft"}`
|
|
7253
|
+
];
|
|
7254
|
+
const iteration = source.iteration;
|
|
7255
|
+
if (iteration !== undefined) {
|
|
7256
|
+
const gate2 = iteration.gate;
|
|
7257
|
+
const codes = gate2.violations.map((v) => v.code).join(", ");
|
|
7258
|
+
lines.push(`iteration ${stripInterpolationHazard(iteration.iterationId)}: gate ${gate2.ok ? "PASS" : `FAIL (${codes})`} | transition ${gate2.transition} | all plans done ${gate2.all_plans_done}`);
|
|
7259
|
+
}
|
|
7260
|
+
const state = source.state;
|
|
7261
|
+
if (state !== null) {
|
|
7262
|
+
const plans = state.plans.length === 0 ? "none" : state.plans.map((p) => `${stripInterpolationHazard(p.id)}(${stripInterpolationHazard(p.status)})`).join(" ");
|
|
7263
|
+
const residuals = state.residuals.length === 0 ? "none" : state.residuals.map((r) => `${r.severity} ${r.count}`).join(", ");
|
|
7264
|
+
const leases = state.leases.length === 0 ? "none active" : state.leases.map((l) => `${stripInterpolationHazard(l.planId)} → ${stripInterpolationHazard(l.holder)}`).join("; ");
|
|
7265
|
+
lines.push(`plans: ${plans} | residuals: ${residuals} | leases: ${leases}`);
|
|
7266
|
+
if (state.direction !== null)
|
|
7267
|
+
lines.push(`direction: ${stripInterpolationHazard(state.direction)}`);
|
|
7268
|
+
}
|
|
7269
|
+
return lines.join(`
|
|
7270
|
+
`);
|
|
7271
|
+
}
|
|
7272
|
+
function errorMessage8(error) {
|
|
7273
|
+
return error instanceof Error ? error.message : String(error);
|
|
7274
|
+
}
|
|
7275
|
+
function log7(level, message) {
|
|
7276
|
+
try {
|
|
7277
|
+
harnessPromptLogSink(level, message);
|
|
7278
|
+
} catch {}
|
|
7279
|
+
}
|
|
7280
|
+
|
|
7281
|
+
// src/index.ts
|
|
7282
|
+
var name = "dsh";
|
|
7283
|
+
var inject = ["loader"];
|
|
7284
|
+
function packagedCommandsDir() {
|
|
7285
|
+
try {
|
|
7286
|
+
const dir = fileURLToPath3(new URL("../harness-commands", import.meta.url));
|
|
7287
|
+
return existsSync18(dir) ? dir : undefined;
|
|
7288
|
+
} catch {
|
|
7289
|
+
return;
|
|
7290
|
+
}
|
|
7291
|
+
}
|
|
7292
|
+
function commandFrontmatterField(frontmatter, label) {
|
|
7293
|
+
const match = new RegExp(`^${label}[ \\t]*:[ \\t]*(.+)$`, "m").exec(frontmatter);
|
|
7294
|
+
return match?.[1]?.trim();
|
|
7295
|
+
}
|
|
7296
|
+
function unquote2(value) {
|
|
7297
|
+
return value.length >= 2 && value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value;
|
|
7298
|
+
}
|
|
7299
|
+
function parseCommandMarkdown(content) {
|
|
7300
|
+
const lines = content.replace(/^\uFEFF/, "").split(/\r?\n/);
|
|
7301
|
+
if (lines.length === 0 || lines[0].trim() !== "---")
|
|
7302
|
+
return;
|
|
7303
|
+
let end = -1;
|
|
7304
|
+
for (let i = 1;i < lines.length; i++) {
|
|
7305
|
+
if (lines[i].trim() === "---") {
|
|
7306
|
+
end = i;
|
|
7307
|
+
break;
|
|
7308
|
+
}
|
|
7309
|
+
}
|
|
7310
|
+
if (end === -1)
|
|
7311
|
+
return;
|
|
7312
|
+
const frontmatter = lines.slice(1, end).join(`
|
|
7313
|
+
`);
|
|
7314
|
+
const name2 = commandFrontmatterField(frontmatter, "name");
|
|
7315
|
+
const description = commandFrontmatterField(frontmatter, "description");
|
|
7316
|
+
if (name2 === undefined || description === undefined)
|
|
7317
|
+
return;
|
|
7318
|
+
const input = commandFrontmatterField(frontmatter, "input");
|
|
7319
|
+
return {
|
|
7320
|
+
name: name2,
|
|
7321
|
+
description: unquote2(description),
|
|
7322
|
+
...input !== undefined && input !== "" ? { input: unquote2(input) } : {},
|
|
7323
|
+
body: lines.slice(end + 1).join(`
|
|
7324
|
+
`).trim()
|
|
7325
|
+
};
|
|
7326
|
+
}
|
|
7327
|
+
function registerMstarCommands(ctx) {
|
|
7328
|
+
const dir = packagedCommandsDir();
|
|
7329
|
+
if (dir === undefined)
|
|
7330
|
+
return;
|
|
7331
|
+
ctx.inject(["commands"], (commandsCtx) => {
|
|
7332
|
+
for (const file of readdirSync9(dir).sort()) {
|
|
7333
|
+
if (!file.endsWith(".md"))
|
|
7334
|
+
continue;
|
|
7335
|
+
const parsed = parseCommandMarkdown(readFileSync15(join20(dir, file), "utf8"));
|
|
5636
7336
|
if (parsed === undefined)
|
|
5637
7337
|
continue;
|
|
5638
7338
|
commandsCtx.commands.register({
|
|
@@ -5675,7 +7375,98 @@ function apply(ctx, config) {
|
|
|
5675
7375
|
else
|
|
5676
7376
|
logger.info(message);
|
|
5677
7377
|
});
|
|
7378
|
+
setDecorationLogger((level, message) => {
|
|
7379
|
+
const logger = ctx.logger(DECORATION_LOGGER);
|
|
7380
|
+
if (level === "debug")
|
|
7381
|
+
logger.debug(message);
|
|
7382
|
+
else if (level === "info")
|
|
7383
|
+
logger.info(message);
|
|
7384
|
+
else
|
|
7385
|
+
logger.warn(message);
|
|
7386
|
+
});
|
|
7387
|
+
setDecorationAgentsDir(packagedAgentsDir());
|
|
7388
|
+
setHarnessPromptLogger((level, message) => {
|
|
7389
|
+
const logger = ctx.logger(HARNESS_PROMPT_LOGGER);
|
|
7390
|
+
if (level === "debug")
|
|
7391
|
+
logger.debug(message);
|
|
7392
|
+
else
|
|
7393
|
+
logger.warn(message);
|
|
7394
|
+
});
|
|
7395
|
+
registerHarnessPrompt(ctx, { resolver });
|
|
7396
|
+
setAdvisoryLogger((level, message) => {
|
|
7397
|
+
const logger = ctx.logger(ADVISORY_LOGGER);
|
|
7398
|
+
if (level === "debug")
|
|
7399
|
+
logger.debug(message);
|
|
7400
|
+
else
|
|
7401
|
+
logger.warn(message);
|
|
7402
|
+
});
|
|
7403
|
+
let advisoryPassed = false;
|
|
7404
|
+
let advisoryPassInFlight = false;
|
|
7405
|
+
let advisoryGeneration = 0;
|
|
7406
|
+
const runAdvisoryPass = () => {
|
|
7407
|
+
if (advisoryPassed || advisoryPassInFlight)
|
|
7408
|
+
return;
|
|
7409
|
+
advisoryPassInFlight = true;
|
|
7410
|
+
const generation = advisoryGeneration;
|
|
7411
|
+
runFallbacksAdvisory(ctx, packagedAgentsDir()).then((ran) => {
|
|
7412
|
+
if (ran && generation === advisoryGeneration)
|
|
7413
|
+
advisoryPassed = true;
|
|
7414
|
+
}).finally(() => {
|
|
7415
|
+
advisoryPassInFlight = false;
|
|
7416
|
+
});
|
|
7417
|
+
};
|
|
7418
|
+
runAdvisoryPass();
|
|
7419
|
+
if (!fallbacksMounted(ctx)) {
|
|
7420
|
+
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)");
|
|
7421
|
+
}
|
|
7422
|
+
ctx.inject(["llm-fallbacks"], (serviceCtx) => {
|
|
7423
|
+
const service = fallbacksService(serviceCtx);
|
|
7424
|
+
if (service === undefined)
|
|
7425
|
+
return;
|
|
7426
|
+
declareMstarSeeds(service, {
|
|
7427
|
+
agentsDir: packagedAgentsDir(),
|
|
7428
|
+
log: (level, message) => {
|
|
7429
|
+
const logger = ctx.logger(SEEDS_LOGGER);
|
|
7430
|
+
if (level === "warn")
|
|
7431
|
+
logger.warn(message);
|
|
7432
|
+
else if (level === "error")
|
|
7433
|
+
logger.error(message);
|
|
7434
|
+
else
|
|
7435
|
+
logger.debug(message);
|
|
7436
|
+
}
|
|
7437
|
+
}).catch((error) => {
|
|
7438
|
+
ctx.logger(SEEDS_LOGGER).error(`mstar seeds declaration failed (contained — fallbacks taxonomy unchanged): ${error instanceof Error ? error.message : String(error)}`);
|
|
7439
|
+
});
|
|
7440
|
+
return () => {
|
|
7441
|
+
advisoryPassed = false;
|
|
7442
|
+
advisoryGeneration += 1;
|
|
7443
|
+
};
|
|
7444
|
+
});
|
|
5678
7445
|
registerSettleListener(ctx, config, pairing);
|
|
7446
|
+
setWorkflowLedgerLogger((level, message) => {
|
|
7447
|
+
const logger = ctx.logger(WORKFLOW_LEDGER_LOGGER);
|
|
7448
|
+
if (level === "warn")
|
|
7449
|
+
logger.warn(message);
|
|
7450
|
+
else
|
|
7451
|
+
logger.debug(message);
|
|
7452
|
+
});
|
|
7453
|
+
registerWorkflowLedger(ctx, resolver, adapter.workflowAskCache);
|
|
7454
|
+
setGoalBridgeLogger((level, message) => {
|
|
7455
|
+
const logger = ctx.logger(GOAL_BRIDGE_LOGGER);
|
|
7456
|
+
if (level === "debug")
|
|
7457
|
+
logger.debug(message);
|
|
7458
|
+
else
|
|
7459
|
+
logger.warn(message);
|
|
7460
|
+
});
|
|
7461
|
+
registerGoalBridge(ctx, resolver, config);
|
|
7462
|
+
setPlanModeBridgeLogger((level, message) => {
|
|
7463
|
+
const logger = ctx.logger(PLAN_MODE_BRIDGE_LOGGER);
|
|
7464
|
+
if (level === "debug")
|
|
7465
|
+
logger.debug(message);
|
|
7466
|
+
else
|
|
7467
|
+
logger.warn(message);
|
|
7468
|
+
});
|
|
7469
|
+
registerPlanModeBridge(ctx, resolver);
|
|
5679
7470
|
ctx.inject(["jobs"], (jobsCtx) => {
|
|
5680
7471
|
const jobs = jobsCtx.jobs;
|
|
5681
7472
|
try {
|
|
@@ -5696,7 +7487,7 @@ function apply(ctx, config) {
|
|
|
5696
7487
|
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
7488
|
}
|
|
5698
7489
|
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
|
|
7490
|
+
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
7491
|
}
|
|
5701
7492
|
ctx.on("fs/write-intent", (target, actor, next) => writeIntentListener(ctx, resolver, config, adapter, target, actor, next), { prepend: true });
|
|
5702
7493
|
ctx.on("fs/edit-intent", (target, actor, next) => editIntentListener(ctx, resolver, config, adapter, target, actor, next), { prepend: true });
|
|
@@ -5706,6 +7497,10 @@ function apply(ctx, config) {
|
|
|
5706
7497
|
ctx.on("fs/write-intent", (target, actor, next) => seamWriteIntentListener(ctx, resolver, config, seam, target, actor, next), { prepend: true });
|
|
5707
7498
|
}
|
|
5708
7499
|
ctx.on("tools/pre-execute", (exec, next) => preExecuteListener(ctx, resolver, config, adapter, exec, next), { prepend: true });
|
|
7500
|
+
ctx.events.on("subagent/start", (info) => {
|
|
7501
|
+
runAdvisoryPass();
|
|
7502
|
+
decorateSubagentStart(ctx, config, info);
|
|
7503
|
+
});
|
|
5709
7504
|
const ttlMs = config.catalogTtlMs ?? DEFAULT_CATALOG_TTL_MS;
|
|
5710
7505
|
const explicitKey = bootHarnessDir !== null ? EXPLICIT_CACHE_KEY : undefined;
|
|
5711
7506
|
const catalogCache = new Map;
|
|
@@ -5723,6 +7518,11 @@ function apply(ctx, config) {
|
|
|
5723
7518
|
}
|
|
5724
7519
|
export {
|
|
5725
7520
|
skillLocalConfig,
|
|
7521
|
+
setDecorationLogger,
|
|
7522
|
+
setDecorationAgentsDir,
|
|
7523
|
+
setAdvisoryLogger,
|
|
7524
|
+
runFallbacksAdvisory,
|
|
7525
|
+
recordWorkflowVerdict,
|
|
5726
7526
|
recordSettle,
|
|
5727
7527
|
recordDispatch,
|
|
5728
7528
|
readAgentFlow,
|
|
@@ -5735,14 +7535,19 @@ export {
|
|
|
5735
7535
|
lintCompoundWrite,
|
|
5736
7536
|
lintAuditWrite,
|
|
5737
7537
|
inject,
|
|
7538
|
+
decorateSubagentStart,
|
|
5738
7539
|
apply,
|
|
5739
7540
|
SkillLintVetoError,
|
|
5740
7541
|
SeamVetoError,
|
|
5741
7542
|
SETTLE_SEAM,
|
|
7543
|
+
PERSONA_SECTION_ORDER,
|
|
7544
|
+
PERSONA_SECTION_NAME,
|
|
5742
7545
|
HarnessResolver,
|
|
5743
7546
|
DshMstar,
|
|
5744
7547
|
DshHostAdapter,
|
|
7548
|
+
DECORATION_LOGGER,
|
|
5745
7549
|
Config,
|
|
5746
7550
|
AGENT_FLOW_MAX_EVENTS,
|
|
5747
|
-
AGENT_FLOW_FILE
|
|
7551
|
+
AGENT_FLOW_FILE,
|
|
7552
|
+
ADVISORY_LOGGER
|
|
5748
7553
|
};
|