@mmerterden/multi-agent-pipeline 20.11.0 → 20.12.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/CHANGELOG.md +22 -0
- package/docs/facts.json +2 -2
- package/install/catalog-history.json +1 -1
- package/install/claude.mjs +9 -0
- package/manifest.json +26 -25
- package/package.json +5 -5
- package/pipeline/agents/code-reviewer.md +1 -1
- package/pipeline/commands/multi-agent/model/SKILL.md +7 -0
- package/pipeline/commands/multi-agent/store-ready/SKILL.md +4 -4
- package/pipeline/commands/multi-agent/sync/SKILL.md +5 -6
- package/pipeline/lib/agent-model-sync.mjs +95 -0
- package/pipeline/lib/model-rung.sh +7 -0
- package/pipeline/multi-agent-refs/features/doctor.md +10 -0
- package/pipeline/multi-agent-refs/features/model-fallback.md +10 -0
- package/pipeline/multi-agent-refs/features/phone-api.md +1 -1
- package/pipeline/multi-agent-refs/features/unattended-security.md +2 -2
- package/pipeline/multi-agent-refs/features/usage-reporting.md +13 -0
- package/pipeline/scripts/doctor.mjs +48 -0
- package/pipeline/scripts/migrate-prefs.mjs +14 -0
- package/pipeline/scripts/unattended_policy.py +12 -5
- package/pipeline/scripts/usage-report.mjs +84 -9
- package/pipeline/skills/.skill-manifest.json +2 -2
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +4 -0
|
@@ -27,7 +27,15 @@
|
|
|
27
27
|
* @module pipeline/scripts/usage-report
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
readFileSync,
|
|
32
|
+
existsSync,
|
|
33
|
+
readdirSync,
|
|
34
|
+
appendFileSync,
|
|
35
|
+
mkdirSync,
|
|
36
|
+
renameSync,
|
|
37
|
+
writeFileSync,
|
|
38
|
+
} from "fs";
|
|
31
39
|
import { canonicalEndpoint } from "../lib/usage-endpoint.mjs";
|
|
32
40
|
import { homedir, platform } from "os";
|
|
33
41
|
import { dirname, join } from "path";
|
|
@@ -709,27 +717,78 @@ function buildEvent(state, { event = null, login = null } = {}) {
|
|
|
709
717
|
};
|
|
710
718
|
}
|
|
711
719
|
|
|
720
|
+
function hostOf(url) {
|
|
721
|
+
try {
|
|
722
|
+
return new URL(url).hostname;
|
|
723
|
+
} catch {
|
|
724
|
+
return null;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* The outcome of one POST. A redirect is never followed, because the ingest
|
|
730
|
+
* token would ride along to wherever it points; its target host is reported
|
|
731
|
+
* instead, so a moved endpoint is visible rather than silent.
|
|
732
|
+
*/
|
|
712
733
|
async function post(endpoint, token, event) {
|
|
713
734
|
const ctrl = new AbortController();
|
|
714
735
|
const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS);
|
|
715
736
|
try {
|
|
716
|
-
await fetch(endpoint, {
|
|
737
|
+
const res = await fetch(endpoint, {
|
|
717
738
|
method: "POST",
|
|
718
739
|
headers: {
|
|
719
740
|
"Content-Type": "application/json",
|
|
720
741
|
"X-Usage-Token": token,
|
|
721
742
|
},
|
|
722
743
|
body: JSON.stringify(event),
|
|
723
|
-
redirect: "
|
|
744
|
+
redirect: "manual",
|
|
724
745
|
signal: ctrl.signal,
|
|
725
746
|
});
|
|
726
|
-
|
|
727
|
-
|
|
747
|
+
if (res.status >= 300 && res.status < 400) {
|
|
748
|
+
return {
|
|
749
|
+
outcome: "redirected",
|
|
750
|
+
status: res.status,
|
|
751
|
+
location: hostOf(res.headers.get("location") || ""),
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
if (!res.ok) return { outcome: "rejected", status: res.status };
|
|
755
|
+
let body = null;
|
|
756
|
+
try {
|
|
757
|
+
body = await res.json();
|
|
758
|
+
} catch {
|
|
759
|
+
/* a 2xx without a JSON body is still a delivery */
|
|
760
|
+
}
|
|
761
|
+
return { outcome: body?.accepted === false ? "not-accepted" : "accepted", status: res.status };
|
|
762
|
+
} catch (err) {
|
|
763
|
+
const reason = ctrl.signal.aborted ? "timeout" : (err?.cause?.code ?? err?.name ?? "error");
|
|
764
|
+
return { outcome: "failed", reason: codeOf(String(reason), 40) };
|
|
728
765
|
} finally {
|
|
729
766
|
clearTimeout(timer);
|
|
730
767
|
}
|
|
731
768
|
}
|
|
732
769
|
|
|
770
|
+
export const LAST_SEND_FILE = "usage-last-send.json";
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Overwrites the record of the most recent send attempt. The emitter runs
|
|
774
|
+
* detached with its output discarded, so this file is the only place a broken
|
|
775
|
+
* reporting path shows up; doctor reads it. It carries no token and no path.
|
|
776
|
+
*/
|
|
777
|
+
function recordSend(entry) {
|
|
778
|
+
const dir = join(homedir(), ".claude", "logs", "multi-agent");
|
|
779
|
+
const target = join(dir, LAST_SEND_FILE);
|
|
780
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
781
|
+
try {
|
|
782
|
+
mkdirSync(dir, { recursive: true });
|
|
783
|
+
writeFileSync(tmp, JSON.stringify({ t: new Date().toISOString(), ...entry }) + "\n", {
|
|
784
|
+
mode: 0o600,
|
|
785
|
+
});
|
|
786
|
+
renameSync(tmp, target);
|
|
787
|
+
} catch {
|
|
788
|
+
/* logs dir unwritable - never break the run */
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
733
792
|
// The write-only ingest token rides in a header; sending it over http would
|
|
734
793
|
// leak it in cleartext, contradicting the setup contract ("transmitted only
|
|
735
794
|
// over TLS"). Accept https anywhere, and http ONLY for loopback (local dev).
|
|
@@ -759,10 +818,16 @@ async function main() {
|
|
|
759
818
|
if (!args.dryRun) {
|
|
760
819
|
if (prefs.optOut === true) return;
|
|
761
820
|
if (prefs.enabled !== true) return;
|
|
762
|
-
if (!endpointAllowed(endpoint))
|
|
821
|
+
if (!endpointAllowed(endpoint)) {
|
|
822
|
+
recordSend({ id: args.taskId, outcome: "skipped", reason: "endpoint-not-allowed" });
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
763
825
|
}
|
|
764
826
|
const token = args.dryRun ? "" : resolveToken();
|
|
765
|
-
if (!args.dryRun && !token)
|
|
827
|
+
if (!args.dryRun && !token) {
|
|
828
|
+
recordSend({ id: args.taskId, outcome: "skipped", reason: "no-token" });
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
766
831
|
|
|
767
832
|
const statePath = resolveStatePath(args);
|
|
768
833
|
let state = statePath ? readJson(statePath) : null;
|
|
@@ -782,7 +847,10 @@ async function main() {
|
|
|
782
847
|
const who = resolveReportingLogin();
|
|
783
848
|
if (!who.login) {
|
|
784
849
|
process.stderr.write(`usage-report: not sent - ${who.reason}\n`);
|
|
785
|
-
if (!args.dryRun)
|
|
850
|
+
if (!args.dryRun) {
|
|
851
|
+
recordSend({ id: runId(state), ev: kind, outcome: "skipped", reason: "no-login" });
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
786
854
|
}
|
|
787
855
|
|
|
788
856
|
const event = buildEvent(state, { event: kind, login: who.login });
|
|
@@ -793,7 +861,14 @@ async function main() {
|
|
|
793
861
|
}
|
|
794
862
|
|
|
795
863
|
if (event.st !== "in_progress") appendErrorLedger(event);
|
|
796
|
-
await post(endpoint, token, event);
|
|
864
|
+
const result = await post(endpoint, token, event);
|
|
865
|
+
recordSend({ id: event.id, ev: event.ev, host: hostOf(endpoint), ...result });
|
|
866
|
+
if (result.outcome !== "accepted") {
|
|
867
|
+
const detail = result.status ?? result.reason;
|
|
868
|
+
const where = result.location ? ` to ${result.location}` : "";
|
|
869
|
+
const verb = result.outcome === "not-accepted" ? "not accepted" : result.outcome;
|
|
870
|
+
process.stderr.write(`usage-report: ${verb}${where} (${detail}) by ${hostOf(endpoint)}\n`);
|
|
871
|
+
}
|
|
797
872
|
}
|
|
798
873
|
|
|
799
874
|
// Entry-point check: compare file URLs, never string-split a path.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
|
-
"generatedAt": "2026-09-
|
|
3
|
+
"generatedAt": "2026-09-29T13:31:43Z",
|
|
4
4
|
"skillCount": 218,
|
|
5
5
|
"entries": [
|
|
6
6
|
{
|
|
@@ -701,7 +701,7 @@
|
|
|
701
701
|
},
|
|
702
702
|
{
|
|
703
703
|
"path": "shared/external/swift-architecture/SKILL.md",
|
|
704
|
-
"sha256": "
|
|
704
|
+
"sha256": "295f6ce1488a93f64e5e78228b90863924fe21911769307844cc73f621c04ab1"
|
|
705
705
|
},
|
|
706
706
|
{
|
|
707
707
|
"path": "shared/external/swift-charts/SKILL.md",
|
|
@@ -13,6 +13,10 @@ SwiftUI with Observation (iOS 17 or later) unless a UIKit type appears.
|
|
|
13
13
|
|
|
14
14
|
## Scope boundary
|
|
15
15
|
|
|
16
|
+
The samples here pass dependencies through initializers. In a repo that resolves
|
|
17
|
+
dependencies from a keyed container, `reference/architecture/dependency-injection.md`
|
|
18
|
+
takes precedence over initializer injection in every sample in this skill.
|
|
19
|
+
|
|
16
20
|
This skill decides:
|
|
17
21
|
|
|
18
22
|
- which pattern a feature uses,
|