@rse/ase 2.0.1 → 2.0.3
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/dst/ase-agent-definition-core.js +111 -87
- package/dst/ase-agent-fleet.js +1 -1
- package/dst/ase-agent-runtime-claim.js +5 -3
- package/dst/ase-agent-runtime-engine.js +1 -1
- package/dst/ase-agent-runtime-phase.js +1 -1
- package/dst/ase-agent-runtime-request.js +1 -1
- package/dst/ase-agent-runtime.js +3 -5
- package/dst/ase-artifact.js +3 -3
- package/dst/ase-config-core.js +2 -16
- package/dst/ase-config-schema.js +1 -3
- package/dst/ase-config.js +4 -1
- package/dst/ase-hook.js +14 -19
- package/dst/ase-lib-daemon-cli.js +117 -4
- package/dst/ase-lib-server-cli.js +10 -10
- package/dst/ase-mcp.js +4 -4
- package/dst/ase-service-getopt.js +2 -2
- package/dst/ase-service-otel.js +4 -4
- package/dst/ase-service-skills.js +5 -15
- package/dst/ase-service.js +87 -210
- package/dst/ase-setup-common.js +1 -2
- package/dst/ase-setup-settings.js +6 -9
- package/dst/ase-setup.js +2 -3
- package/dst/ase-statusline.js +170 -170
- package/dst/ase-task-board-core.js +1 -1
- package/dst/ase-task-board-flow.js +7 -6
- package/dst/ase-task-board-graph.js +5 -4
- package/dst/ase-task-board-tui-control.js +51 -45
- package/dst/ase-task-board-tui-model.js +195 -181
- package/dst/ase-task-board-tui-popup.js +2 -3
- package/dst/ase-task-board-tui-view.js +88 -79
- package/dst/ase-task-board-web-client.css +1 -1
- package/dst/ase-task-board-web-client.js +2 -2
- package/dst/ase-task-board-web-server.js +4 -27
- package/dst/ase-task-board.js +1 -1
- package/dst/ase-task-format.js +1 -1
- package/dst/ase-task-store-client.js +17 -5
- package/dst/ase-task-store-core.js +5 -7
- package/dst/ase-task-store-issues.js +91 -0
- package/dst/ase-task-store-plugin-ase.js +7 -3
- package/dst/ase-task-store-plugin-gitea.js +11 -97
- package/dst/ase-task-store-plugin-github.js +5 -77
- package/dst/ase-task-store-plugin-gitlab.js +4 -83
- package/dst/ase-task.js +51 -23
- package/dst/ase-util-compat.js +1 -1
- package/dst/ase-util-diagram.js +16 -42
- package/dst/ase.js +5 -5
- package/dst/man/ase-agent-adhoc.1 +1 -1
- package/dst/man/ase-agent-condenser.1 +1 -1
- package/dst/man/ase-agent-editor.1 +1 -1
- package/dst/man/ase-agent-estimator.1 +1 -1
- package/dst/man/ase-agent-fleet.1 +1 -1
- package/dst/man/ase-agent-implementer.1 +1 -1
- package/dst/man/ase-agent-integrator.1 +1 -1
- package/dst/man/ase-agent-qualifier.1 +1 -1
- package/dst/man/ase-agent-rebooter.1 +1 -1
- package/dst/man/ase-agent-relay.1 +1 -1
- package/dst/man/ase-agent-researcher.1 +1 -1
- package/dst/man/ase-agent.1 +1 -1
- package/dst/man/ase-artifact-list.1 +1 -1
- package/dst/man/ase-artifact-name.1 +1 -1
- package/dst/man/ase-artifact.1 +1 -1
- package/dst/man/ase-config-delete.1 +1 -1
- package/dst/man/ase-config-edit.1 +1 -1
- package/dst/man/ase-config-get.1 +1 -1
- package/dst/man/ase-config-init.1 +1 -1
- package/dst/man/ase-config-list.1 +1 -1
- package/dst/man/ase-config-set.1 +1 -1
- package/dst/man/ase-config.1 +1 -1
- package/dst/man/ase-hook-permission-request.1 +1 -1
- package/dst/man/ase-hook-pre-tool-use.1 +1 -1
- package/dst/man/ase-hook-session-end.1 +1 -1
- package/dst/man/ase-hook-session-persona.1 +1 -1
- package/dst/man/ase-hook-session-start.1 +1 -1
- package/dst/man/ase-hook-stop.1 +1 -1
- package/dst/man/ase-hook-user-prompt-submit.1 +1 -1
- package/dst/man/ase-hook.1 +1 -1
- package/dst/man/ase-man.1 +1 -1
- package/dst/man/ase-mcp.1 +1 -1
- package/dst/man/ase-service-log.1 +1 -1
- package/dst/man/ase-service-send.1 +1 -1
- package/dst/man/ase-service-start.1 +1 -1
- package/dst/man/ase-service-status.1 +1 -1
- package/dst/man/ase-service-stop.1 +1 -1
- package/dst/man/ase-service.1 +1 -1
- package/dst/man/ase-setup-disable.1 +1 -1
- package/dst/man/ase-setup-enable.1 +1 -1
- package/dst/man/ase-setup-install.1 +1 -1
- package/dst/man/ase-setup-mcp.1 +1 -1
- package/dst/man/ase-setup-status.1 +1 -1
- package/dst/man/ase-setup-statusline.1 +1 -1
- package/dst/man/ase-setup-telemetry.1 +1 -1
- package/dst/man/ase-setup-uninstall.1 +1 -1
- package/dst/man/ase-setup-update.1 +1 -1
- package/dst/man/ase-setup.1 +1 -1
- package/dst/man/ase-spec-export.1 +1 -1
- package/dst/man/ase-spec-lint.1 +1 -1
- package/dst/man/ase-spec-preview.1 +1 -1
- package/dst/man/ase-spec.1 +1 -1
- package/dst/man/ase-statusline.1 +1 -1
- package/dst/man/ase-task-assign.1 +1 -1
- package/dst/man/ase-task-board.1 +3 -3
- package/dst/man/ase-task-delete.1 +1 -1
- package/dst/man/ase-task-edit.1 +1 -1
- package/dst/man/ase-task-idscheme.1 +1 -1
- package/dst/man/ase-task-lifecycle.1 +1 -1
- package/dst/man/ase-task-list.1 +1 -1
- package/dst/man/ase-task-load.1 +1 -1
- package/dst/man/ase-task-newid.1 +1 -1
- package/dst/man/ase-task-purge.1 +1 -1
- package/dst/man/ase-task-rename.1 +1 -1
- package/dst/man/ase-task-request.1 +1 -1
- package/dst/man/ase-task-save.1 +1 -1
- package/dst/man/ase-task-status.1 +1 -1
- package/dst/man/ase-task-store.1 +1 -1
- package/dst/man/ase-task-view.1 +1 -1
- package/dst/man/ase-task.1 +1 -1
- package/dst/man/ase-util-compat.1 +1 -1
- package/dst/man/ase-util-diagram.1 +1 -1
- package/dst/man/ase-util-meta.1 +1 -1
- package/dst/man/ase-util-metric.1 +1 -1
- package/dst/man/ase-util-mint.1 +1 -1
- package/dst/man/ase-util-worktree.1 +1 -1
- package/dst/man/ase-util.1 +1 -1
- package/dst/man/ase.1 +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.github/plugin/plugin.json +1 -1
- package/plugin/meta/ase-common-resolve.md +2 -2
- package/plugin/meta/ase-skill.md +7 -0
- package/plugin/mods/ase-mods.ts +16 -12
- package/plugin/skills/ase-arch-analyze/SKILL.md +3 -1
- package/plugin/skills/ase-code-analyze/SKILL.md +26 -10
- package/plugin/skills/ase-code-analyze/help.md +11 -6
- package/plugin/skills/ase-code-edit/SKILL.md +5 -4
- package/plugin/skills/ase-code-edit/help.md +1 -1
- package/plugin/skills/ase-code-lint/SKILL.md +52 -27
- package/plugin/skills/ase-code-lint/help.md +10 -5
- package/plugin/skills/ase-help-intent/data.md +60 -30
- package/plugin/skills/ase-help-skill/help.md +3 -4
- package/plugin/skills/ase-meta-quorum/SKILL.md +9 -0
- package/plugin/skills/ase-meta-quorum/help.md +5 -3
- package/plugin/skills/ase-meta-workflow/sample.md +5 -3
- package/plugin/skills/ase-repo-changelog/SKILL.md +8 -9
- package/plugin/skills/ase-repo-changelog/help.md +2 -2
- package/plugin/skills/ase-repo-review/SKILL.md +5 -3
- package/plugin/skills/ase-repo-review/help.md +3 -1
- package/plugin/skills/ase-spec-edit/SKILL.md +2 -1
- package/plugin/skills/ase-spec-edit/help.md +2 -1
- package/plugin/skills/ase-sync-reconcile/help.md +4 -4
- package/plugin/skills/ase-task-dissect/SKILL.md +10 -6
- package/plugin/skills/ase-task-grill/help.md +1 -1
- package/plugin/skills/ase-task-implement/SKILL.md +58 -1
- package/plugin/skills/ase-task-implement/help.md +16 -0
- package/plugin/skills/ase-task-list/help.md +2 -2
|
@@ -47,7 +47,7 @@ export const discoverKinds = (log, reserved) => {
|
|
|
47
47
|
}
|
|
48
48
|
const seen = new Set();
|
|
49
49
|
const rank = (entry) => definitionExts.indexOf(path.extname(entry).toLowerCase());
|
|
50
|
-
for (const entry of entries.filter((
|
|
50
|
+
for (const entry of entries.filter((e) => rank(e) >= 0).sort((a, b) => rank(a) - rank(b))) {
|
|
51
51
|
const kind = path.basename(entry, path.extname(entry));
|
|
52
52
|
const file = path.join(dir, entry);
|
|
53
53
|
if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(kind) || kind.length > 64)
|
|
@@ -403,11 +403,11 @@ const loadDefinition = async (spec, chain = []) => {
|
|
|
403
403
|
if (spec.startsWith("builtin:")) {
|
|
404
404
|
const where = `agent: option "--definition": definition "${spec}"`;
|
|
405
405
|
const m = /^builtin:([a-z]+)(?:-([a-z]+))?$/.exec(spec);
|
|
406
|
-
const kind = builtinKinds.find((
|
|
406
|
+
const kind = builtinKinds.find((k) => k.kind === m?.[1]);
|
|
407
407
|
const mode = m?.[2] ?? kind?.mode ?? "";
|
|
408
408
|
if (m === null || kind === undefined || !Object.hasOwn(kind.modes, mode))
|
|
409
409
|
throw new Error(`${where}: unknown built-in definition (expected ` +
|
|
410
|
-
builtinKinds.flatMap((
|
|
410
|
+
builtinKinds.flatMap((k) => Object.keys(k.modes).map((n) => `"builtin:${k.kind}-${n}"`)).join(", ") + ")");
|
|
411
411
|
id = `builtin:${kind.kind}-${mode}`;
|
|
412
412
|
loaded = definitionValues(kind.modes[mode], where, "");
|
|
413
413
|
}
|
|
@@ -576,6 +576,106 @@ const resolveStates = (str, list, has, type, unclaimed) => {
|
|
|
576
576
|
}
|
|
577
577
|
return { incoming, activity, success, failure, phase, passed, verdicts, lifecycles };
|
|
578
578
|
};
|
|
579
|
+
/* resolve the prompt of the processing type <type> through the option accessors <str>
|
|
580
|
+
and <has>: the skill (if any) and the parsed template of its arguments, of the prompt
|
|
581
|
+
text, or of the prompt file, with the declared parameters <params> */
|
|
582
|
+
const resolvePrompt = (str, has, type, params) => {
|
|
583
|
+
const fail = failOption;
|
|
584
|
+
const prompts = ["prompt-skill", "prompt-text", "prompt-file"].filter((key) => has(key));
|
|
585
|
+
if (prompts.length !== 1)
|
|
586
|
+
fail(prompts[1] ?? "prompt-skill", "exactly one of \"--prompt-skill\", \"--prompt-text\", or \"--prompt-file\" is required");
|
|
587
|
+
if (has("prompt-args") && !has("prompt-skill"))
|
|
588
|
+
fail("prompt-args", "allowed with \"--prompt-skill\" only");
|
|
589
|
+
const skill = str("prompt-skill");
|
|
590
|
+
if (has("prompt-skill") && !/^(?:[A-Za-z0-9_.-]+:)?[A-Za-z0-9_.-]+$/.test(skill))
|
|
591
|
+
fail("prompt-skill", `invalid skill name "${skill}"`);
|
|
592
|
+
let template;
|
|
593
|
+
if (has("prompt-skill"))
|
|
594
|
+
template = parseTemplate(str("prompt-args", type === "phase" ? "{id}" : "{id}: {prompt}"), "prompt-args", type, params);
|
|
595
|
+
else if (has("prompt-text"))
|
|
596
|
+
template = parseTemplate(str("prompt-text"), "prompt-text", type, params);
|
|
597
|
+
else {
|
|
598
|
+
let text = "";
|
|
599
|
+
try {
|
|
600
|
+
text = fs.readFileSync(path.resolve(str("prompt-file")), "utf8");
|
|
601
|
+
}
|
|
602
|
+
catch (err) {
|
|
603
|
+
fail("prompt-file", `cannot read prompt file: ${err instanceof Error ? err.message : String(err)}`);
|
|
604
|
+
}
|
|
605
|
+
template = parseTemplate(text, "prompt-file", type, params);
|
|
606
|
+
}
|
|
607
|
+
return { skill, template };
|
|
608
|
+
};
|
|
609
|
+
/* resolve the success criteria, the output line matches, and the accepted verdicts of the
|
|
610
|
+
processing type <type> through the option accessors <list> and <has>, validated against
|
|
611
|
+
the determination of a verdict <verdicting> and the target states per verdict <verdicts> */
|
|
612
|
+
const resolveSuccess = (list, has, type, unclaimed, verdicting, verdicts) => {
|
|
613
|
+
const fail = failOption;
|
|
614
|
+
const criteria = has("success-criterion") ? [...new Set(list("success-criterion"))] :
|
|
615
|
+
(type === "phase" ? ["exit", "clean"] : ["clean", "changed"]);
|
|
616
|
+
for (const criterion of criteria) {
|
|
617
|
+
if (!definitionCriteria.includes(criterion))
|
|
618
|
+
fail("success-criterion", `invalid criterion "${criterion}" ` +
|
|
619
|
+
`(expected ${definitionCriteria.map((c) => `"${c}"`).join(", ")})`);
|
|
620
|
+
else if (criterion === "changed" && type !== "request")
|
|
621
|
+
fail("success-criterion", "criterion \"changed\" is for \"--type request\" only");
|
|
622
|
+
else if (criterion === "status" && type !== "phase")
|
|
623
|
+
fail("success-criterion", "criterion \"status\" is for \"--type phase\" only");
|
|
624
|
+
else if (criterion === "status" && unclaimed)
|
|
625
|
+
fail("success-criterion", "criterion \"status\" is not for \"--unclaimed\" (as an unclaimed task has no phase success state)");
|
|
626
|
+
else if (criterion === "verdict" && !verdicting)
|
|
627
|
+
fail("success-criterion", "criterion \"verdict\" requires \"--verdict-label\" (or the hook \"verdict\")");
|
|
628
|
+
else if (criterion === "attachment" && !has("attach-expect"))
|
|
629
|
+
fail("success-criterion", "criterion \"attachment\" requires \"--attach-expect\"");
|
|
630
|
+
}
|
|
631
|
+
const match = list("success-match").map((spec) => {
|
|
632
|
+
try {
|
|
633
|
+
return new RegExp(spec);
|
|
634
|
+
}
|
|
635
|
+
catch (err) {
|
|
636
|
+
return fail("success-match", `invalid regular expression "${spec}": ${err instanceof Error ? err.message : String(err)}`);
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
if ((Object.keys(verdicts).length > 0 || has("verdict-allow")) && !verdicting)
|
|
640
|
+
fail(Object.keys(verdicts).length > 0 ? "state-verdict" : "verdict-allow", "requires \"--verdict-label\" (or the hook \"verdict\")");
|
|
641
|
+
const allow = has("verdict-allow") ? list("verdict-allow") : Object.keys(verdicts);
|
|
642
|
+
for (const verdict of allow)
|
|
643
|
+
if (!/^[A-Z]+$/.test(verdict))
|
|
644
|
+
fail("verdict-allow", `invalid verdict "${verdict}" (expected an upper-case word)`);
|
|
645
|
+
for (const verdict of Object.keys(verdicts))
|
|
646
|
+
if (!allow.includes(verdict))
|
|
647
|
+
fail("state-verdict", `verdict "${verdict}" is not accepted by "--verdict-allow"`);
|
|
648
|
+
return { criteria: criteria, match, allow };
|
|
649
|
+
};
|
|
650
|
+
/* resolve the attachments through the option accessors <str> and <has>: the expected
|
|
651
|
+
attachment and the attachment leaving a task unclaimed, validated together with the
|
|
652
|
+
kinds of the output and change set attachments */
|
|
653
|
+
const resolveAttach = (str, has) => {
|
|
654
|
+
const fail = failOption;
|
|
655
|
+
const kindRe = /^[A-Za-z0-9][A-Za-z0-9_.:-]*$/;
|
|
656
|
+
const attachSpec = (key) => {
|
|
657
|
+
if (!has(key))
|
|
658
|
+
return null;
|
|
659
|
+
const spec = str(key);
|
|
660
|
+
const i = spec.indexOf(";");
|
|
661
|
+
const mime = i < 0 ? "" : spec.slice(0, i).trim().toLowerCase();
|
|
662
|
+
const kind = i < 0 ? spec.trim() : spec.slice(i + 1).trim();
|
|
663
|
+
if (!kindRe.test(kind) || (i >= 0 && !/^[a-z0-9.+-]+\/[a-z0-9.+-]+$/.test(mime)))
|
|
664
|
+
fail(key, `invalid attachment "${spec}" (expected "[<mime-type>;]<kind>")`);
|
|
665
|
+
return { mime, kind };
|
|
666
|
+
};
|
|
667
|
+
if (has("attach-output") && !kindRe.test(str("attach-output")))
|
|
668
|
+
fail("attach-output", `invalid kind "${str("attach-output")}"`);
|
|
669
|
+
else if (str("attach-output") === "agent")
|
|
670
|
+
fail("attach-output", "kind \"agent\" is reserved for the processing log");
|
|
671
|
+
if (has("attach-changeset") && !kindRe.test(str("attach-changeset")))
|
|
672
|
+
fail("attach-changeset", `invalid kind "${str("attach-changeset")}"`);
|
|
673
|
+
const expect = attachSpec("attach-expect");
|
|
674
|
+
if (expect !== null && has("attach-output")
|
|
675
|
+
&& attachMatches(expect, `text/plain; kind="${str("attach-output")}"`))
|
|
676
|
+
fail("attach-expect", `attachment "${str("attach-expect")}" is always met by the output attachment of "--attach-output"`);
|
|
677
|
+
return { expect, skip: attachSpec("attach-skip") };
|
|
678
|
+
};
|
|
579
679
|
/* resolve the definition of the agent named <name> from its (camel-cased) command-line
|
|
580
680
|
options <opts>: the defaults, overridden by the definition of "--definition" (if any),
|
|
581
681
|
overridden by the given options, validated against the task lifecycle models */
|
|
@@ -707,88 +807,12 @@ export const resolveDefinition = async (opts, name) => {
|
|
|
707
807
|
/* the parameters (see "resolveParams") */
|
|
708
808
|
const decls = loaded.params;
|
|
709
809
|
const params = resolveParams(decls, list("params"));
|
|
710
|
-
/* the prompt */
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
const skill = str("prompt-skill");
|
|
717
|
-
if (has("prompt-skill") && !/^(?:[A-Za-z0-9_.-]+:)?[A-Za-z0-9_.-]+$/.test(skill))
|
|
718
|
-
fail("prompt-skill", `invalid skill name "${skill}"`);
|
|
719
|
-
let template;
|
|
720
|
-
if (has("prompt-skill"))
|
|
721
|
-
template = parseTemplate(str("prompt-args", type === "phase" ? "{id}" : "{id}: {prompt}"), "prompt-args", type, Object.keys(decls));
|
|
722
|
-
else if (has("prompt-text"))
|
|
723
|
-
template = parseTemplate(str("prompt-text"), "prompt-text", type, Object.keys(decls));
|
|
724
|
-
else {
|
|
725
|
-
let text = "";
|
|
726
|
-
try {
|
|
727
|
-
text = fs.readFileSync(path.resolve(str("prompt-file")), "utf8");
|
|
728
|
-
}
|
|
729
|
-
catch (err) {
|
|
730
|
-
fail("prompt-file", `cannot read prompt file: ${err instanceof Error ? err.message : String(err)}`);
|
|
731
|
-
}
|
|
732
|
-
template = parseTemplate(text, "prompt-file", type, Object.keys(decls));
|
|
733
|
-
}
|
|
734
|
-
/* the success criteria and the verdict */
|
|
735
|
-
const criteria = has("success-criterion") ? [...new Set(list("success-criterion"))] :
|
|
736
|
-
(type === "phase" ? ["exit", "clean"] : ["clean", "changed"]);
|
|
737
|
-
for (const criterion of criteria) {
|
|
738
|
-
if (!definitionCriteria.includes(criterion))
|
|
739
|
-
fail("success-criterion", `invalid criterion "${criterion}" ` +
|
|
740
|
-
`(expected ${definitionCriteria.map((c) => `"${c}"`).join(", ")})`);
|
|
741
|
-
else if (criterion === "changed" && type !== "request")
|
|
742
|
-
fail("success-criterion", "criterion \"changed\" is for \"--type request\" only");
|
|
743
|
-
else if (criterion === "status" && type !== "phase")
|
|
744
|
-
fail("success-criterion", "criterion \"status\" is for \"--type phase\" only");
|
|
745
|
-
else if (criterion === "status" && unclaimed)
|
|
746
|
-
fail("success-criterion", "criterion \"status\" is not for \"--unclaimed\" (as an unclaimed task has no phase success state)");
|
|
747
|
-
else if (criterion === "verdict" && !verdicting)
|
|
748
|
-
fail("success-criterion", "criterion \"verdict\" requires \"--verdict-label\" (or the hook \"verdict\")");
|
|
749
|
-
else if (criterion === "attachment" && !has("attach-expect"))
|
|
750
|
-
fail("success-criterion", "criterion \"attachment\" requires \"--attach-expect\"");
|
|
751
|
-
}
|
|
752
|
-
const match = list("success-match").map((spec) => {
|
|
753
|
-
try {
|
|
754
|
-
return new RegExp(spec);
|
|
755
|
-
}
|
|
756
|
-
catch (err) {
|
|
757
|
-
return fail("success-match", `invalid regular expression "${spec}": ${err instanceof Error ? err.message : String(err)}`);
|
|
758
|
-
}
|
|
759
|
-
});
|
|
760
|
-
if ((Object.keys(verdicts).length > 0 || has("verdict-allow")) && !verdicting)
|
|
761
|
-
fail(Object.keys(verdicts).length > 0 ? "state-verdict" : "verdict-allow", "requires \"--verdict-label\" (or the hook \"verdict\")");
|
|
762
|
-
const allow = has("verdict-allow") ? list("verdict-allow") : Object.keys(verdicts);
|
|
763
|
-
for (const verdict of allow)
|
|
764
|
-
if (!/^[A-Z]+$/.test(verdict))
|
|
765
|
-
fail("verdict-allow", `invalid verdict "${verdict}" (expected an upper-case word)`);
|
|
766
|
-
for (const verdict of Object.keys(verdicts))
|
|
767
|
-
if (!allow.includes(verdict))
|
|
768
|
-
fail("state-verdict", `verdict "${verdict}" is not accepted by "--verdict-allow"`);
|
|
769
|
-
/* the attachments */
|
|
770
|
-
const kindRe = /^[A-Za-z0-9][A-Za-z0-9_.:-]*$/;
|
|
771
|
-
const attachSpec = (key) => {
|
|
772
|
-
if (!has(key))
|
|
773
|
-
return null;
|
|
774
|
-
const spec = str(key);
|
|
775
|
-
const i = spec.indexOf(";");
|
|
776
|
-
const mime = i < 0 ? "" : spec.slice(0, i).trim().toLowerCase();
|
|
777
|
-
const kind = i < 0 ? spec.trim() : spec.slice(i + 1).trim();
|
|
778
|
-
if (!kindRe.test(kind) || (i >= 0 && !/^[a-z0-9.+-]+\/[a-z0-9.+-]+$/.test(mime)))
|
|
779
|
-
fail(key, `invalid attachment "${spec}" (expected "[<mime-type>;]<kind>")`);
|
|
780
|
-
return { mime, kind };
|
|
781
|
-
};
|
|
782
|
-
if (has("attach-output") && !kindRe.test(str("attach-output")))
|
|
783
|
-
fail("attach-output", `invalid kind "${str("attach-output")}"`);
|
|
784
|
-
else if (str("attach-output") === "agent")
|
|
785
|
-
fail("attach-output", "kind \"agent\" is reserved for the processing log");
|
|
786
|
-
if (has("attach-changeset") && !kindRe.test(str("attach-changeset")))
|
|
787
|
-
fail("attach-changeset", `invalid kind "${str("attach-changeset")}"`);
|
|
788
|
-
const attachExpect = attachSpec("attach-expect");
|
|
789
|
-
if (attachExpect !== null && has("attach-output")
|
|
790
|
-
&& attachMatches(attachExpect, `text/plain; kind="${str("attach-output")}"`))
|
|
791
|
-
fail("attach-expect", `attachment "${str("attach-expect")}" is always met by the output attachment of "--attach-output"`);
|
|
810
|
+
/* the prompt (see "resolvePrompt") */
|
|
811
|
+
const { skill, template } = resolvePrompt(str, has, type, Object.keys(decls));
|
|
812
|
+
/* the success criteria and the verdict (see "resolveSuccess") */
|
|
813
|
+
const { criteria, match, allow } = resolveSuccess(list, has, type, unclaimed, verdicting, verdicts);
|
|
814
|
+
/* the attachments (see "resolveAttach") */
|
|
815
|
+
const { expect: attachExpect, skip: attachSkip } = resolveAttach(str, has);
|
|
792
816
|
for (const key of ["prepare-run", "finish-success", "finish-failure"])
|
|
793
817
|
if ((has(key) && str(key, "-") === "") || list(key).some((cmd) => cmd.trim() === ""))
|
|
794
818
|
fail(key, "empty shell command");
|
|
@@ -814,10 +838,10 @@ export const resolveDefinition = async (opts, name) => {
|
|
|
814
838
|
request: { name: requestName, done: str("request-done", "processed"), prompt: consumes },
|
|
815
839
|
prompt: { skill, template },
|
|
816
840
|
prepare: list("prepare-run"),
|
|
817
|
-
success: { criteria
|
|
841
|
+
success: { criteria, match },
|
|
818
842
|
verdict: { label: str("verdict-label"), allow },
|
|
819
843
|
attach: {
|
|
820
|
-
output: str("attach-output"), expect: attachExpect, skip:
|
|
844
|
+
output: str("attach-output"), expect: attachExpect, skip: attachSkip,
|
|
821
845
|
changeset: str("attach-changeset")
|
|
822
846
|
},
|
|
823
847
|
finish: { commit: flag("finish-commit"), success: str("finish-success"), failure: str("finish-failure") },
|
package/dst/ase-agent-fleet.js
CHANGED
|
@@ -335,7 +335,7 @@ export default class AgentFleetCommand extends DaemonCommand {
|
|
|
335
335
|
const { kind = name, ...options } = section(name);
|
|
336
336
|
if (typeof kind !== "string" || !this.kinds.has(kind))
|
|
337
337
|
throw new Error(`fleet: section "${name}": unknown agent kind "${String(kind)}" ` +
|
|
338
|
-
`(expected ${kinds.map((
|
|
338
|
+
`(expected ${kinds.map((k) => `"${k}"`).join(", ")})`);
|
|
339
339
|
validateName(name, kind, kinds);
|
|
340
340
|
/* the options of the definition of an agent can be nested by group, and the
|
|
341
341
|
definition file of an ad-hoc agent (but not a built-in definition) is
|
|
@@ -51,7 +51,9 @@ export class AgentRun {
|
|
|
51
51
|
record(run) {
|
|
52
52
|
try {
|
|
53
53
|
const data = JSON.parse(fs.readFileSync(this.file(run, ".json"), "utf8"));
|
|
54
|
-
|
|
54
|
+
const claims = data.claims !== null && typeof data.claims === "object" && !Array.isArray(data.claims) ?
|
|
55
|
+
data.claims : {};
|
|
56
|
+
return { pid: Number(data.pid ?? 0), start: String(data.start ?? ""), claims };
|
|
55
57
|
}
|
|
56
58
|
catch (_err) {
|
|
57
59
|
return { pid: 0, start: "", claims: {} };
|
|
@@ -112,12 +114,12 @@ export class AgentRun {
|
|
|
112
114
|
.filter((entry) => /\.(?:lock|json)$/.test(entry))
|
|
113
115
|
.map((entry) => entry.replace(/\.(?:lock|json)$/, "")))];
|
|
114
116
|
}
|
|
115
|
-
/* the terminated runs of the agent kind for the task store
|
|
117
|
+
/* the terminated runs of the agent kind for the task store */
|
|
116
118
|
async dead() {
|
|
117
119
|
const dead = [];
|
|
118
120
|
for (const run of this.runs())
|
|
119
121
|
if (!(await this.alive(run)))
|
|
120
|
-
dead.push(
|
|
122
|
+
dead.push(run);
|
|
121
123
|
return dead;
|
|
122
124
|
}
|
|
123
125
|
/* seize a terminated run for its recovery, by taking over its (stale or removed) run lock,
|
|
@@ -412,7 +412,7 @@ class DefinedCore {
|
|
|
412
412
|
};
|
|
413
413
|
}
|
|
414
414
|
catch (err) {
|
|
415
|
-
await work.cleanup?.(log).catch((err2) => log("warning",
|
|
415
|
+
await work.cleanup?.(log).catch((err2) => log("warning", reason(err2)));
|
|
416
416
|
throw err;
|
|
417
417
|
}
|
|
418
418
|
}
|
|
@@ -515,7 +515,7 @@ export class PhaseAgent extends Agent {
|
|
|
515
515
|
is removed once all its tasks are recovered */
|
|
516
516
|
async recover() {
|
|
517
517
|
const { incoming, success } = this.states;
|
|
518
|
-
for (const
|
|
518
|
+
for (const run of await this.runLock.dead()) {
|
|
519
519
|
const claims = await this.runLock.seize(run);
|
|
520
520
|
if (claims === null)
|
|
521
521
|
continue;
|
|
@@ -100,7 +100,7 @@ export class RequestAgent extends Agent {
|
|
|
100
100
|
});
|
|
101
101
|
await this.write("warning", this.name, `task "${item.id}": ${this.requests.name} request of terminated ${this.role} marked as failed`);
|
|
102
102
|
}
|
|
103
|
-
for (const
|
|
103
|
+
for (const run of runs)
|
|
104
104
|
await this.runLock.purge(run);
|
|
105
105
|
}
|
|
106
106
|
/* select every task with a request (except one whose finalization is still retried),
|
package/dst/ase-agent-runtime.js
CHANGED
|
@@ -840,9 +840,7 @@ export class Agent {
|
|
|
840
840
|
const cli = toolSpecs[tool].cli;
|
|
841
841
|
const cmd = job.cmd ?? "";
|
|
842
842
|
const i = cmd.indexOf(" ");
|
|
843
|
-
const args = toolArgs[tool](job.prompt ?? skillPrompt(tool, i < 0 ? cmd : cmd.slice(0, i), i < 0 ? "" : cmd.slice(i + 1)), (job.dirs ?? [])
|
|
844
|
-
.filter((dir, i, dirs) => dir !== cwd && dirs.indexOf(dir) === i)
|
|
845
|
-
.flatMap((dir) => ["--add-dir", dir]));
|
|
843
|
+
const args = toolArgs[tool](job.prompt ?? skillPrompt(tool, i < 0 ? cmd : cmd.slice(0, i), i < 0 ? "" : cmd.slice(i + 1)), [...new Set(job.dirs ?? [])].filter((dir) => dir !== cwd).flatMap((dir) => ["--add-dir", dir]));
|
|
846
844
|
await log("info", `running: ${cli} ${args.map((arg) => arg.includes(" ") ? `"${arg}"` : arg).join(" ")}`);
|
|
847
845
|
const started = Date.now();
|
|
848
846
|
const costs = { costToken: null, costUSD: null, cread: 0, cwrite: 0, input: 0, apiMs: null };
|
|
@@ -1041,8 +1039,8 @@ export class Agent {
|
|
|
1041
1039
|
...Object.fromEntries(Object.entries(own).filter((entry) => entry[1] !== undefined))
|
|
1042
1040
|
};
|
|
1043
1041
|
this._name = opts.name ?? "";
|
|
1044
|
-
if (this._name !== "" && !/^[A-Za-z0-9][A-Za-z0-9_.-]
|
|
1045
|
-
throw new Error(`agent: invalid name "${this._name}" (expected letters, digits, "_", ".", or "-")`);
|
|
1042
|
+
if (this._name !== "" && !/^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/.test(this._name))
|
|
1043
|
+
throw new Error(`agent: invalid name "${this._name}" (expected up to 64 letters, digits, "_", ".", or "-")`);
|
|
1046
1044
|
this.relay = relay;
|
|
1047
1045
|
this.tool = parseTool(opts.tool);
|
|
1048
1046
|
for (const cli of [toolSpecs[this.tool].cli, ...(this.worktree ? ["git"] : [])])
|
package/dst/ase-artifact.js
CHANGED
|
@@ -149,7 +149,7 @@ export class Artifact {
|
|
|
149
149
|
/* resolve a base-relative "filename" within a kind's "basedir" to a
|
|
150
150
|
project-root-relative POSIX path; the implicit "othr" catch-all
|
|
151
151
|
has no configured "basedir" and is therefore rejected */
|
|
152
|
-
static
|
|
152
|
+
static resolveName(log, kind, filename) {
|
|
153
153
|
if (kind === "othr")
|
|
154
154
|
throw new Error("artifact: kind \"othr\" has no configured basedir");
|
|
155
155
|
const file = filename.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
@@ -216,7 +216,7 @@ export default class ArtifactCommand {
|
|
|
216
216
|
`(${configuredKinds.join("|")})`, "code")
|
|
217
217
|
.action(async (filename, opts) => {
|
|
218
218
|
const kind = Artifact.validateKind(opts.kind.trim());
|
|
219
|
-
await writeStdout(`${Artifact.
|
|
219
|
+
await writeStdout(`${Artifact.resolveName(this.log, kind, filename)}\n`);
|
|
220
220
|
});
|
|
221
221
|
}
|
|
222
222
|
}
|
|
@@ -285,7 +285,7 @@ export class ArtifactMCP {
|
|
|
285
285
|
}, async (args) => {
|
|
286
286
|
try {
|
|
287
287
|
const kind = Artifact.validateKind(args.kind ?? "code");
|
|
288
|
-
const result = { name: Artifact.
|
|
288
|
+
const result = { name: Artifact.resolveName(this.log, kind, args.filename) };
|
|
289
289
|
return {
|
|
290
290
|
structuredContent: result,
|
|
291
291
|
content: [{ type: "text", text: JSON.stringify(result) }]
|
package/dst/ase-config-core.js
CHANGED
|
@@ -427,15 +427,8 @@ export class Config {
|
|
|
427
427
|
next.setIn(prefix, next.createNode({}));
|
|
428
428
|
}
|
|
429
429
|
next.setIn(segments, value);
|
|
430
|
-
|
|
430
|
+
this.validateDoc(next, td.filename, "strict");
|
|
431
431
|
td.doc = next;
|
|
432
|
-
try {
|
|
433
|
-
this.validateDoc(td.doc, td.filename, "strict");
|
|
434
|
-
}
|
|
435
|
-
catch (err) {
|
|
436
|
-
td.doc = saved;
|
|
437
|
-
throw err;
|
|
438
|
-
}
|
|
439
432
|
}
|
|
440
433
|
/* delete a value at a dotted key from the target scope */
|
|
441
434
|
delete(key) {
|
|
@@ -445,15 +438,8 @@ export class Config {
|
|
|
445
438
|
return;
|
|
446
439
|
const next = td.doc.clone();
|
|
447
440
|
next.deleteIn(this.resolveKey(key).split("."));
|
|
448
|
-
|
|
441
|
+
this.validateDoc(next, td.filename, "strict");
|
|
449
442
|
td.doc = next;
|
|
450
|
-
try {
|
|
451
|
-
this.validateDoc(td.doc, td.filename, "strict");
|
|
452
|
-
}
|
|
453
|
-
catch (err) {
|
|
454
|
-
td.doc = saved;
|
|
455
|
-
throw err;
|
|
456
|
-
}
|
|
457
443
|
}
|
|
458
444
|
}
|
|
459
445
|
/* access the internal "state.yaml" of a session, which, unlike the
|
package/dst/ase-config-schema.js
CHANGED
|
@@ -5,9 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import * as v from "valibot";
|
|
7
7
|
import { foregroundColorNames } from "chalk";
|
|
8
|
-
import { checkIdScheme } from "./ase-task-format.js";
|
|
9
|
-
import { parseEffortScale } from "./ase-task-format.js";
|
|
10
|
-
import { EFFORT_SCALE } from "./ase-task-format.js";
|
|
8
|
+
import { checkIdScheme, parseEffortScale, EFFORT_SCALE } from "./ase-task-format.js";
|
|
11
9
|
/* classification taxonomy */
|
|
12
10
|
export const projectClassification = {
|
|
13
11
|
boxing: ["white", "grey", "black"],
|
package/dst/ase-config.js
CHANGED
|
@@ -191,7 +191,10 @@ export class ConfigMCP {
|
|
|
191
191
|
const cfg = new Config("config", configSchema, this.log, scope);
|
|
192
192
|
cfg.read();
|
|
193
193
|
const val = cfg.get(args.key);
|
|
194
|
-
|
|
194
|
+
if (isMap(val))
|
|
195
|
+
throw new Error(`key "${args.key}" is not a leaf key`);
|
|
196
|
+
const text = val === undefined ? "" :
|
|
197
|
+
JSON.stringify(configSecretKeys.includes(cfg.resolveKey(args.key)) ? "***" : val);
|
|
195
198
|
return { content: [{ type: "text", text }] };
|
|
196
199
|
}
|
|
197
200
|
catch (err) {
|
package/dst/ase-hook.js
CHANGED
|
@@ -16,6 +16,8 @@ import { parseScope, userStateDir } from "./ase-config-scope.js";
|
|
|
16
16
|
import { readStdin, writeStdout } from "./ase-lib-stdio.js";
|
|
17
17
|
import { Task } from "./ase-task.js";
|
|
18
18
|
import * as TaskFormat from "./ase-task-format.js";
|
|
19
|
+
import { parseTool } from "./ase-setup-common.js";
|
|
20
|
+
/* the addon MCP servers whose tools get auto-approved */
|
|
19
21
|
const addonMcpServers = [
|
|
20
22
|
"chat-alibaba-qwen",
|
|
21
23
|
"chat-deepseek",
|
|
@@ -37,6 +39,7 @@ const addonMcpToolNamePattern = (prefix, suffix) => {
|
|
|
37
39
|
.join("|");
|
|
38
40
|
return new RegExp(`^${prefix}(?:${alternatives})${suffix}`);
|
|
39
41
|
};
|
|
42
|
+
/* the per-tool dispatch table of the hook integrations */
|
|
40
43
|
const toolSpecs = {
|
|
41
44
|
"claude": {
|
|
42
45
|
toolNameField: "tool_name",
|
|
@@ -88,7 +91,7 @@ export default class HookCommand {
|
|
|
88
91
|
}
|
|
89
92
|
/* validate a session id against the accepted character set */
|
|
90
93
|
isValidSessionId(id) {
|
|
91
|
-
return /^[A-Za-z0-9._-]+$/.test(id);
|
|
94
|
+
return /^[A-Za-z0-9._-]+$/.test(id) && id !== "." && id !== "..";
|
|
92
95
|
}
|
|
93
96
|
/* resolve the base directory holding all per-session state */
|
|
94
97
|
sessionBaseDir() {
|
|
@@ -105,7 +108,7 @@ export default class HookCommand {
|
|
|
105
108
|
try {
|
|
106
109
|
entries = fs.readdirSync(base, { withFileTypes: true });
|
|
107
110
|
}
|
|
108
|
-
catch
|
|
111
|
+
catch {
|
|
109
112
|
/* best-effort: no base directory yet, or unreadable */
|
|
110
113
|
return;
|
|
111
114
|
}
|
|
@@ -120,7 +123,7 @@ export default class HookCommand {
|
|
|
120
123
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
121
124
|
this.log.write("debug", `hook: pruned stale session directory: ${dir}`);
|
|
122
125
|
}
|
|
123
|
-
catch
|
|
126
|
+
catch {
|
|
124
127
|
/* best-effort: ignore vanished or undeletable directories */
|
|
125
128
|
}
|
|
126
129
|
}
|
|
@@ -142,7 +145,7 @@ export default class HookCommand {
|
|
|
142
145
|
try {
|
|
143
146
|
raw = JSON.parse(text);
|
|
144
147
|
}
|
|
145
|
-
catch
|
|
148
|
+
catch {
|
|
146
149
|
/* best-effort: return empty object on malformed JSON */
|
|
147
150
|
return empty;
|
|
148
151
|
}
|
|
@@ -167,7 +170,7 @@ export default class HookCommand {
|
|
|
167
170
|
try {
|
|
168
171
|
content = fs.readFileSync(abs, "utf8");
|
|
169
172
|
}
|
|
170
|
-
catch
|
|
173
|
+
catch {
|
|
171
174
|
return match;
|
|
172
175
|
}
|
|
173
176
|
const next = new Set(visited);
|
|
@@ -199,7 +202,7 @@ export default class HookCommand {
|
|
|
199
202
|
try {
|
|
200
203
|
return this.pluginRoot(tool);
|
|
201
204
|
}
|
|
202
|
-
catch
|
|
205
|
+
catch {
|
|
203
206
|
return "";
|
|
204
207
|
}
|
|
205
208
|
}
|
|
@@ -428,7 +431,7 @@ export default class HookCommand {
|
|
|
428
431
|
ASE_HEADLESS: headless,
|
|
429
432
|
ASE_AGENT_TOOL: tool
|
|
430
433
|
});
|
|
431
|
-
/* prepend ASE information to constitution markdown */
|
|
434
|
+
/* prepend ASE information to the constitution markdown (with its @<file> references expanded) */
|
|
432
435
|
let context = `<ase-version>${version}</ase-version>\n` +
|
|
433
436
|
`<ase-version-hint>${versionHint}</ase-version-hint>\n` +
|
|
434
437
|
`<ase-plugin-root>${pluginRoot}</ase-plugin-root>\n` +
|
|
@@ -447,9 +450,7 @@ export default class HookCommand {
|
|
|
447
450
|
`<ase-session-id>${sessionId}</ase-session-id>\n` +
|
|
448
451
|
`<ase-headless>${headless}</ase-headless>\n` +
|
|
449
452
|
`<ase-agent-tool>${tool}</ase-agent-tool>\n` +
|
|
450
|
-
"\n" + md;
|
|
451
|
-
/* expand all @<file> references manually */
|
|
452
|
-
context = this.expandReferences(context, mdDir);
|
|
453
|
+
"\n" + this.expandReferences(md, mdDir);
|
|
453
454
|
/* append the output style to the constitution markdown (GitHub
|
|
454
455
|
Copilot CLI and OpenAI Codex CLI only -- Anthropic Claude Code CLI
|
|
455
456
|
applies the plugin output style natively), stripping the
|
|
@@ -530,7 +531,7 @@ export default class HookCommand {
|
|
|
530
531
|
try {
|
|
531
532
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
532
533
|
}
|
|
533
|
-
catch
|
|
534
|
+
catch {
|
|
534
535
|
/* best-effort: ignore failures */
|
|
535
536
|
}
|
|
536
537
|
/* detach the session from its AI usage window (if any), which closes
|
|
@@ -577,7 +578,7 @@ export default class HookCommand {
|
|
|
577
578
|
});
|
|
578
579
|
return val;
|
|
579
580
|
}
|
|
580
|
-
catch
|
|
581
|
+
catch {
|
|
581
582
|
return "";
|
|
582
583
|
}
|
|
583
584
|
}
|
|
@@ -706,12 +707,6 @@ export default class HookCommand {
|
|
|
706
707
|
}
|
|
707
708
|
return 0;
|
|
708
709
|
}
|
|
709
|
-
/* parse and validate the --tool option */
|
|
710
|
-
parseTool(value) {
|
|
711
|
-
if (value !== "claude" && value !== "copilot" && value !== "codex")
|
|
712
|
-
throw new Error(`invalid --tool value: "${value}" (expected "claude", "copilot", or "codex")`);
|
|
713
|
-
return value;
|
|
714
|
-
}
|
|
715
710
|
/* register commands */
|
|
716
711
|
register(program) {
|
|
717
712
|
/* default for --tool derived from ASE_TOOL environment variable
|
|
@@ -743,7 +738,7 @@ export default class HookCommand {
|
|
|
743
738
|
.description(desc)
|
|
744
739
|
.option("-t, --tool <tool>", "target tool (\"claude\", \"copilot\", or \"codex\")", toolDflt)
|
|
745
740
|
.action(async (opts) => {
|
|
746
|
-
process.exitCode = await handler(
|
|
741
|
+
process.exitCode = await handler(parseTool(opts.tool));
|
|
747
742
|
});
|
|
748
743
|
}
|
|
749
744
|
}
|