@triflux/remote 10.50.1 → 10.50.2
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/cto/collect.mjs +16 -174
- package/cto/current.schema.json +4 -16
- package/cto/events.mjs +13 -294
- package/cto/hygiene.mjs +26 -346
- package/cto/lake-root.mjs +114 -16
- package/cto/status.mjs +31 -78
- package/hub/store-adapter.mjs +1 -1
- package/hub/team/conductor.mjs +1 -1
- package/hub/team/execution-mode.mjs +1 -1
- package/hub/team/headless.mjs +1 -1
- package/hub/team/psmux.mjs +16 -402
- package/hub/team/session.mjs +1 -28
- package/hub/team/terminal-opener.mjs +11 -35
- package/package.json +1 -1
- package/scripts/lib/gemini-profiles.mjs +1 -1
- package/scripts/lib/legacy-mcp-cleanup.mjs +14 -3
- package/scripts/lib/psmux-info.mjs +18 -49
- package/scripts/lib/skill-template.mjs +0 -26
- package/cto/hygiene-actions.mjs +0 -355
- package/cto/hygiene-notify.mjs +0 -170
- package/hub/workers/gemini-worker.mjs +0 -217
- package/scripts/lib/agent-json.mjs +0 -27
- package/scripts/lib/async.mjs +0 -174
- package/scripts/lib/cli-agy.mjs +0 -85
- package/scripts/lib/cli-codex.mjs +0 -62
- package/scripts/lib/cli-gemini.mjs +0 -37
- package/scripts/lib/env.mjs +0 -14
- package/scripts/lib/hub.mjs +0 -48
- package/scripts/lib/pid.mjs +0 -63
- package/scripts/lib/quota.mjs +0 -47
- package/scripts/lib/skill-state.mjs +0 -220
- package/scripts/lib/team.mjs +0 -75
- package/scripts/lib/timeout.mjs +0 -66
- package/scripts/lib/tmp.mjs +0 -26
- package/scripts/lib/toml.mjs +0 -132
package/cto/collect.mjs
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import {
|
|
3
|
-
appendFileSync,
|
|
4
|
-
closeSync,
|
|
5
3
|
existsSync,
|
|
6
4
|
mkdirSync,
|
|
7
|
-
openSync,
|
|
8
5
|
readdirSync,
|
|
9
6
|
readFileSync,
|
|
10
|
-
renameSync,
|
|
11
7
|
statSync,
|
|
12
|
-
unlinkSync,
|
|
13
|
-
writeFileSync,
|
|
14
8
|
} from "node:fs";
|
|
15
9
|
import { homedir } from "node:os";
|
|
16
|
-
import {
|
|
10
|
+
import { join, relative } from "node:path";
|
|
17
11
|
import { fileURLToPath } from "node:url";
|
|
18
12
|
|
|
19
13
|
import { renderBrief } from "./brief.mjs";
|
|
20
|
-
import {
|
|
14
|
+
import { appendCtoEvent } from "./events.mjs";
|
|
15
|
+
import {
|
|
16
|
+
firstExisting,
|
|
17
|
+
readJsonLines,
|
|
18
|
+
resolveLakeRootDir,
|
|
19
|
+
writeAtomic,
|
|
20
|
+
} from "./lake-root.mjs";
|
|
21
21
|
|
|
22
22
|
const SCHEMA_VERSION = "cto-lake.v1";
|
|
23
23
|
const SOURCE_REGISTRY = [
|
|
@@ -69,24 +69,6 @@ const SOURCE_REGISTRY = [
|
|
|
69
69
|
probe: ".omx/handoffs/*",
|
|
70
70
|
enabled: true,
|
|
71
71
|
},
|
|
72
|
-
{
|
|
73
|
-
id: "session_vault",
|
|
74
|
-
kind: "optional-durable-ref",
|
|
75
|
-
probe: ".triflux/session-vault.json, sessions_v2.db, or .session-vault",
|
|
76
|
-
enabled: true,
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
id: "agy",
|
|
80
|
-
kind: "optional-durable-ref",
|
|
81
|
-
probe: ".agy/state.json or .agy/refs.json",
|
|
82
|
-
enabled: true,
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
id: "gbrain",
|
|
86
|
-
kind: "optional-durable-ref",
|
|
87
|
-
probe: ".gbrain/refs.json or .gbrain/config.json",
|
|
88
|
-
enabled: true,
|
|
89
|
-
},
|
|
90
72
|
];
|
|
91
73
|
const SOURCE_IDS = SOURCE_REGISTRY.map((source) => source.id);
|
|
92
74
|
|
|
@@ -104,10 +86,6 @@ function isoNow(opts) {
|
|
|
104
86
|
return value.toISOString();
|
|
105
87
|
}
|
|
106
88
|
|
|
107
|
-
function sleep(ms) {
|
|
108
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
109
|
-
}
|
|
110
|
-
|
|
111
89
|
function sourceState(available, status, detail, collectedAt) {
|
|
112
90
|
return {
|
|
113
91
|
available,
|
|
@@ -130,25 +108,6 @@ function readJson(filePath) {
|
|
|
130
108
|
return JSON.parse(readFileSync(filePath, "utf8"));
|
|
131
109
|
}
|
|
132
110
|
|
|
133
|
-
function readJsonLines(filePath, limit = 20) {
|
|
134
|
-
if (!existsSync(filePath)) return [];
|
|
135
|
-
const lines = readFileSync(filePath, "utf8")
|
|
136
|
-
.split(/\r?\n/u)
|
|
137
|
-
.map((line) => line.trim())
|
|
138
|
-
.filter(Boolean);
|
|
139
|
-
return lines.slice(-limit).flatMap((line) => {
|
|
140
|
-
try {
|
|
141
|
-
return [JSON.parse(line)];
|
|
142
|
-
} catch {
|
|
143
|
-
return [];
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function findFirstExisting(candidates) {
|
|
149
|
-
return candidates.find((candidate) => existsSync(candidate)) || null;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
111
|
function listDirFiles(dirPath, rootDir, limit = 10) {
|
|
153
112
|
if (!existsSync(dirPath)) return [];
|
|
154
113
|
try {
|
|
@@ -237,7 +196,7 @@ function collectJsonArtifact(
|
|
|
237
196
|
collectedAt,
|
|
238
197
|
summarize = safeSummary,
|
|
239
198
|
) {
|
|
240
|
-
const filePath =
|
|
199
|
+
const filePath = firstExisting(candidates);
|
|
241
200
|
if (!filePath) {
|
|
242
201
|
return missingSource(
|
|
243
202
|
"no_shell_readable_artifact",
|
|
@@ -429,59 +388,6 @@ function collectHandoffs(rootDir, collectedAt) {
|
|
|
429
388
|
);
|
|
430
389
|
}
|
|
431
390
|
|
|
432
|
-
function collectDurableRefs(
|
|
433
|
-
id,
|
|
434
|
-
candidates,
|
|
435
|
-
rootDir,
|
|
436
|
-
collectedAt,
|
|
437
|
-
unavailableStatus = "no_shell_readable_artifact",
|
|
438
|
-
) {
|
|
439
|
-
const filePath = findFirstExisting(candidates);
|
|
440
|
-
if (!filePath) {
|
|
441
|
-
return missingSource(
|
|
442
|
-
unavailableStatus,
|
|
443
|
-
`${id} exposes no shell-readable durable artifact`,
|
|
444
|
-
collectedAt,
|
|
445
|
-
);
|
|
446
|
-
}
|
|
447
|
-
try {
|
|
448
|
-
const stat = statSync(filePath);
|
|
449
|
-
if (stat.isDirectory()) {
|
|
450
|
-
return sourceState(
|
|
451
|
-
true,
|
|
452
|
-
"ok",
|
|
453
|
-
{
|
|
454
|
-
path: relPath(rootDir, filePath),
|
|
455
|
-
files: listDirFiles(filePath, rootDir, 10),
|
|
456
|
-
},
|
|
457
|
-
collectedAt,
|
|
458
|
-
);
|
|
459
|
-
}
|
|
460
|
-
const parsed = basename(filePath).endsWith(".json")
|
|
461
|
-
? readJson(filePath)
|
|
462
|
-
: readFileSync(filePath, "utf8");
|
|
463
|
-
return sourceState(
|
|
464
|
-
true,
|
|
465
|
-
"ok",
|
|
466
|
-
{
|
|
467
|
-
path: relPath(rootDir, filePath),
|
|
468
|
-
summary: safeSummary(parsed),
|
|
469
|
-
},
|
|
470
|
-
collectedAt,
|
|
471
|
-
);
|
|
472
|
-
} catch (error) {
|
|
473
|
-
return sourceState(
|
|
474
|
-
true,
|
|
475
|
-
"read_error",
|
|
476
|
-
{
|
|
477
|
-
path: relPath(rootDir, filePath),
|
|
478
|
-
error: error?.message || `${id} read failed`,
|
|
479
|
-
},
|
|
480
|
-
collectedAt,
|
|
481
|
-
);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
391
|
function buildSummary(current) {
|
|
486
392
|
const repo = current.repo;
|
|
487
393
|
const omxGoals = current.sources.ultragoal_omx.detail?.active_goals || [];
|
|
@@ -633,48 +539,6 @@ function validateCurrent(current) {
|
|
|
633
539
|
}
|
|
634
540
|
}
|
|
635
541
|
|
|
636
|
-
function writeAtomic(filePath, body) {
|
|
637
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
638
|
-
const tmpPath = join(
|
|
639
|
-
dirname(filePath),
|
|
640
|
-
`.${basename(filePath)}.${process.pid}.${Date.now()}.tmp`,
|
|
641
|
-
);
|
|
642
|
-
writeFileSync(tmpPath, body, "utf8");
|
|
643
|
-
renameSync(tmpPath, filePath);
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
async function appendLedgerEvent(lakeRoot, event, stderr) {
|
|
647
|
-
mkdirSync(lakeRoot, { recursive: true });
|
|
648
|
-
const ledgerPath = join(lakeRoot, "ledger.jsonl");
|
|
649
|
-
const lockPath = join(lakeRoot, "ledger.jsonl.lock");
|
|
650
|
-
let fd = null;
|
|
651
|
-
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
652
|
-
try {
|
|
653
|
-
fd = openSync(lockPath, "wx", 0o600);
|
|
654
|
-
break;
|
|
655
|
-
} catch (error) {
|
|
656
|
-
if (error?.code !== "EEXIST") throw error;
|
|
657
|
-
if (attempt < 2) await sleep(100);
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
if (fd === null) {
|
|
661
|
-
stderr.write(
|
|
662
|
-
"[tfx cto collect] warning: ledger lock timeout; skipped ledger append\n",
|
|
663
|
-
);
|
|
664
|
-
return false;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
try {
|
|
668
|
-
appendFileSync(ledgerPath, `${JSON.stringify(event)}\n`, "utf8");
|
|
669
|
-
return true;
|
|
670
|
-
} finally {
|
|
671
|
-
closeSync(fd);
|
|
672
|
-
try {
|
|
673
|
-
unlinkSync(lockPath);
|
|
674
|
-
} catch {}
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
|
|
678
542
|
function collectSources(rootDir, collectedAt, execFileSyncFn, opts = {}) {
|
|
679
543
|
const home = homedir();
|
|
680
544
|
const hostArtifactCandidates =
|
|
@@ -748,34 +612,6 @@ function collectSources(rootDir, collectedAt, execFileSyncFn, opts = {}) {
|
|
|
748
612
|
collectedAt,
|
|
749
613
|
),
|
|
750
614
|
handoffs: collectHandoffs(rootDir, collectedAt),
|
|
751
|
-
session_vault: collectDurableRefs(
|
|
752
|
-
"session_vault",
|
|
753
|
-
[
|
|
754
|
-
join(rootDir, ".triflux", "session-vault.json"),
|
|
755
|
-
join(rootDir, "sessions_v2.db"),
|
|
756
|
-
join(rootDir, ".session-vault"),
|
|
757
|
-
],
|
|
758
|
-
rootDir,
|
|
759
|
-
collectedAt,
|
|
760
|
-
"not_shell_collectable",
|
|
761
|
-
),
|
|
762
|
-
agy: collectDurableRefs(
|
|
763
|
-
"agy",
|
|
764
|
-
[join(rootDir, ".agy", "state.json"), join(rootDir, ".agy", "refs.json")],
|
|
765
|
-
rootDir,
|
|
766
|
-
collectedAt,
|
|
767
|
-
"not_shell_collectable",
|
|
768
|
-
),
|
|
769
|
-
gbrain: collectDurableRefs(
|
|
770
|
-
"gbrain",
|
|
771
|
-
[
|
|
772
|
-
join(rootDir, ".gbrain", "refs.json"),
|
|
773
|
-
join(rootDir, ".gbrain", "config.json"),
|
|
774
|
-
],
|
|
775
|
-
rootDir,
|
|
776
|
-
collectedAt,
|
|
777
|
-
"not_shell_collectable",
|
|
778
|
-
),
|
|
779
615
|
};
|
|
780
616
|
|
|
781
617
|
return { repo: git.repo, sources };
|
|
@@ -786,6 +622,12 @@ function hasFlag(args, flag) {
|
|
|
786
622
|
}
|
|
787
623
|
|
|
788
624
|
export async function runCollect(args = [], opts = {}) {
|
|
625
|
+
if (hasFlag(args, "--help")) {
|
|
626
|
+
(opts.stdout || process.stdout).write(
|
|
627
|
+
"Usage: tfx cto collect [--json]\nRefresh the CTO lake from durable authority sources.\n",
|
|
628
|
+
);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
789
631
|
const rootDir = opts.rootDir || resolveLakeRootDir(process.cwd());
|
|
790
632
|
const lakeRoot = opts.lakeRoot || join(rootDir, ".triflux", "lake");
|
|
791
633
|
const stdout = opts.stdout || process.stdout;
|
|
@@ -822,7 +664,7 @@ export async function runCollect(args = [], opts = {}) {
|
|
|
822
664
|
sources_json: "sources.json",
|
|
823
665
|
},
|
|
824
666
|
};
|
|
825
|
-
const appended = await
|
|
667
|
+
const { appended } = await appendCtoEvent(lakeRoot, event, { stderr });
|
|
826
668
|
if (appended) current.ledger_tail = readLedgerTail(lakeRoot);
|
|
827
669
|
|
|
828
670
|
validateCurrent(current);
|
package/cto/current.schema.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://triflux.local/schemas/cto/current.schema.json",
|
|
4
4
|
"title": "Triflux CTO Lake Current Authority State",
|
|
5
|
-
"description": "Contract for .triflux/lake/current.json. .triflux/lake is a triflux-owned, namespace-neutral cross-agent rollup for durable artifacts across Claude, Codex/OMX, OMC,
|
|
5
|
+
"description": "Contract for .triflux/lake/current.json. .triflux/lake is a triflux-owned, namespace-neutral cross-agent rollup for durable artifacts across Claude, Codex/OMX, OMC, and Triflux state. It reads and aggregates .omx/ultragoal and .omc/ultragoal execution ledgers without replacing or duplicating their authority. ledger.jsonl and sources.json companion contracts are documented in $defs.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
8
8
|
"required": [
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"sources": {
|
|
46
46
|
"type": "object",
|
|
47
|
-
"description": "Durable artifact source states only.
|
|
47
|
+
"description": "Durable artifact source states only. A source reports available:false when no shell-readable durable artifact exists.",
|
|
48
48
|
"additionalProperties": false,
|
|
49
49
|
"required": [
|
|
50
50
|
"git",
|
|
@@ -54,10 +54,7 @@
|
|
|
54
54
|
"tfx_synapse",
|
|
55
55
|
"ultragoal_omx",
|
|
56
56
|
"ultragoal_omc",
|
|
57
|
-
"handoffs"
|
|
58
|
-
"session_vault",
|
|
59
|
-
"agy",
|
|
60
|
-
"gbrain"
|
|
57
|
+
"handoffs"
|
|
61
58
|
],
|
|
62
59
|
"properties": {
|
|
63
60
|
"git": {
|
|
@@ -83,21 +80,12 @@
|
|
|
83
80
|
},
|
|
84
81
|
"handoffs": {
|
|
85
82
|
"$ref": "#/$defs/sourceState"
|
|
86
|
-
},
|
|
87
|
-
"session_vault": {
|
|
88
|
-
"$ref": "#/$defs/sourceState"
|
|
89
|
-
},
|
|
90
|
-
"agy": {
|
|
91
|
-
"$ref": "#/$defs/sourceState"
|
|
92
|
-
},
|
|
93
|
-
"gbrain": {
|
|
94
|
-
"$ref": "#/$defs/sourceState"
|
|
95
83
|
}
|
|
96
84
|
}
|
|
97
85
|
},
|
|
98
86
|
"summary": {
|
|
99
87
|
"type": "object",
|
|
100
|
-
"description": "Human-
|
|
88
|
+
"description": "Human-oriented synthesized authority summary derived from durable artifacts and live Triflux synapse session status. Phase 1 may add source-derived fields here without changing the top-level contract.",
|
|
101
89
|
"additionalProperties": true
|
|
102
90
|
},
|
|
103
91
|
"ledger_tail": {
|
package/cto/events.mjs
CHANGED
|
@@ -6,12 +6,13 @@ import {
|
|
|
6
6
|
unlinkSync,
|
|
7
7
|
} from "node:fs";
|
|
8
8
|
import { basename, join } from "node:path";
|
|
9
|
-
import {
|
|
9
|
+
import { pathLabel, shortHash, toIsoTime } from "./lake-root.mjs";
|
|
10
10
|
|
|
11
11
|
export const CTO_EVENT_SCHEMA_VERSION = "cto-event.v1";
|
|
12
12
|
export const DEFAULT_CTO_EVENT_SOURCE = "tfx_cto_event";
|
|
13
13
|
|
|
14
14
|
export const CTO_EVENT_TYPES = Object.freeze([
|
|
15
|
+
"collect",
|
|
15
16
|
"session_started",
|
|
16
17
|
"session_heartbeat",
|
|
17
18
|
"session_stale",
|
|
@@ -42,98 +43,6 @@ export const CTO_HYGIENE_STATUSES = Object.freeze([
|
|
|
42
43
|
const EVENT_TYPE_SET = new Set(CTO_EVENT_TYPES);
|
|
43
44
|
const STATUS_SET = new Set(CTO_HYGIENE_STATUSES);
|
|
44
45
|
|
|
45
|
-
const EVENT_PRESETS = Object.freeze({
|
|
46
|
-
"context-save": {
|
|
47
|
-
event: "checkpoint_saved",
|
|
48
|
-
source: "gstack_context_save",
|
|
49
|
-
actor: { cli: "gstack context-save" },
|
|
50
|
-
ownerSurface: "gstack context-save -> tfx cto event context-save",
|
|
51
|
-
},
|
|
52
|
-
"checkpoint-saved": {
|
|
53
|
-
event: "checkpoint_saved",
|
|
54
|
-
ownerSurface: "checkpoint wrapper -> tfx cto event checkpoint-saved",
|
|
55
|
-
},
|
|
56
|
-
"context-restore": {
|
|
57
|
-
event: "checkpoint_restored",
|
|
58
|
-
source: "gstack_context_restore",
|
|
59
|
-
actor: { cli: "gstack context-restore" },
|
|
60
|
-
ownerSurface: "gstack context-restore -> tfx cto event context-restore",
|
|
61
|
-
},
|
|
62
|
-
"checkpoint-restored": {
|
|
63
|
-
event: "checkpoint_restored",
|
|
64
|
-
ownerSurface: "checkpoint wrapper -> tfx cto event checkpoint-restored",
|
|
65
|
-
},
|
|
66
|
-
"pr-created": {
|
|
67
|
-
event: "pr_created",
|
|
68
|
-
source: "tfx_pr_lifecycle",
|
|
69
|
-
actor: { cli: "gh pr create" },
|
|
70
|
-
ownerSurface: "gh pr create/merge/close -> tfx cto event pr-created",
|
|
71
|
-
},
|
|
72
|
-
"pr-merged": {
|
|
73
|
-
event: "pr_merged_or_closed",
|
|
74
|
-
source: "tfx_pr_lifecycle",
|
|
75
|
-
status: "completed",
|
|
76
|
-
actor: { cli: "gh pr merge" },
|
|
77
|
-
ownerSurface:
|
|
78
|
-
"gh pr create/merge/close -> tfx cto event pr-merged-or-closed",
|
|
79
|
-
},
|
|
80
|
-
"pr-closed": {
|
|
81
|
-
event: "pr_merged_or_closed",
|
|
82
|
-
source: "tfx_pr_lifecycle",
|
|
83
|
-
status: "completed",
|
|
84
|
-
actor: { cli: "gh pr close" },
|
|
85
|
-
ownerSurface:
|
|
86
|
-
"gh pr create/merge/close -> tfx cto event pr-merged-or-closed",
|
|
87
|
-
},
|
|
88
|
-
"pr-merged-or-closed": {
|
|
89
|
-
event: "pr_merged_or_closed",
|
|
90
|
-
source: "tfx_pr_lifecycle",
|
|
91
|
-
actor: { cli: "gh pr merge/close" },
|
|
92
|
-
ownerSurface:
|
|
93
|
-
"gh pr create/merge/close -> tfx cto event pr-merged-or-closed",
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
const OPTION_KEYS = Object.freeze({
|
|
98
|
-
event: "event",
|
|
99
|
-
"event-type": "event",
|
|
100
|
-
source: "source",
|
|
101
|
-
summary: "summary",
|
|
102
|
-
now: "now",
|
|
103
|
-
ts: "ts",
|
|
104
|
-
status: "status",
|
|
105
|
-
"session-id": "session_id",
|
|
106
|
-
"parent-session-id": "parent_session_id",
|
|
107
|
-
"restored-from-session-id": "restored_from_session_id",
|
|
108
|
-
"checkpoint-id": "checkpoint_id",
|
|
109
|
-
"artifact-path": "artifact_path",
|
|
110
|
-
artifact: "artifact_path",
|
|
111
|
-
"task-id": "task_id",
|
|
112
|
-
branch: "branch",
|
|
113
|
-
"last-seen-at": "last_seen_at",
|
|
114
|
-
"stale-reason": "stale_reason",
|
|
115
|
-
"hygiene-key": "hygiene_key",
|
|
116
|
-
"hygiene-kind": "hygiene_kind",
|
|
117
|
-
"hygiene-id": "hygiene_id",
|
|
118
|
-
"hygiene-action": "hygiene_action",
|
|
119
|
-
"project-root": "project_root",
|
|
120
|
-
"worktree-path": "worktree_path",
|
|
121
|
-
worktree: "worktree_path",
|
|
122
|
-
issue: "issue_refs",
|
|
123
|
-
"issue-ref": "issue_refs",
|
|
124
|
-
"issue-refs": "issue_refs",
|
|
125
|
-
pr: "pr_refs",
|
|
126
|
-
"pr-ref": "pr_refs",
|
|
127
|
-
"pr-refs": "pr_refs",
|
|
128
|
-
"actor-cli": "actor.cli",
|
|
129
|
-
"actor-session-id": "actor.session_id",
|
|
130
|
-
"actor-agent-id": "actor.agent_id",
|
|
131
|
-
"actor-host": "actor.host",
|
|
132
|
-
"owner-surface": "owner_surface",
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
const COLLECTION_KEYS = new Set(["issue_refs", "pr_refs"]);
|
|
136
|
-
|
|
137
46
|
function sleep(ms) {
|
|
138
47
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
139
48
|
}
|
|
@@ -144,31 +53,6 @@ function maybeString(value) {
|
|
|
144
53
|
return trimmed || null;
|
|
145
54
|
}
|
|
146
55
|
|
|
147
|
-
function toIsoTime(value) {
|
|
148
|
-
if (typeof value === "string" && value.trim()) return value;
|
|
149
|
-
if (value instanceof Date) return value.toISOString();
|
|
150
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
151
|
-
return new Date(value).toISOString();
|
|
152
|
-
}
|
|
153
|
-
return new Date().toISOString();
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function shortHash(value) {
|
|
157
|
-
const str = String(value ?? "");
|
|
158
|
-
let h = 5381;
|
|
159
|
-
for (let i = 0; i < str.length; i += 1) {
|
|
160
|
-
h = (h * 33) ^ str.charCodeAt(i);
|
|
161
|
-
}
|
|
162
|
-
return (h >>> 0).toString(36);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function pathLabel(value) {
|
|
166
|
-
const str = String(value ?? "").replace(/[/\\]+$/u, "");
|
|
167
|
-
if (!str) return null;
|
|
168
|
-
const segments = str.split(/[/\\]+/u);
|
|
169
|
-
return segments[segments.length - 1] || null;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
56
|
function normalizeNumericRefs(values) {
|
|
173
57
|
const rawValues = Array.isArray(values)
|
|
174
58
|
? values
|
|
@@ -200,124 +84,6 @@ function normalizeActor(actor) {
|
|
|
200
84
|
return Object.keys(normalized).length > 0 ? normalized : null;
|
|
201
85
|
}
|
|
202
86
|
|
|
203
|
-
function optionName(raw) {
|
|
204
|
-
return String(raw || "")
|
|
205
|
-
.replace(/^--?/u, "")
|
|
206
|
-
.trim();
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function appendCollection(target, key, value) {
|
|
210
|
-
const values = String(value ?? "")
|
|
211
|
-
.split(",")
|
|
212
|
-
.map((item) => item.trim())
|
|
213
|
-
.filter(Boolean);
|
|
214
|
-
if (values.length === 0) return;
|
|
215
|
-
if (!Array.isArray(target[key])) target[key] = [];
|
|
216
|
-
target[key].push(...values);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function putPathValue(target, keyPath, value) {
|
|
220
|
-
if (keyPath.startsWith("actor.")) {
|
|
221
|
-
const key = keyPath.slice("actor.".length);
|
|
222
|
-
target.actor ||= {};
|
|
223
|
-
target.actor[key] = value;
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
if (COLLECTION_KEYS.has(keyPath)) {
|
|
227
|
-
appendCollection(target, keyPath, value);
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
target[keyPath] = value;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function parseEventArgs(args = []) {
|
|
234
|
-
const fields = {};
|
|
235
|
-
const positional = [];
|
|
236
|
-
let json = false;
|
|
237
|
-
|
|
238
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
239
|
-
const arg = args[i];
|
|
240
|
-
if (arg === "--json") {
|
|
241
|
-
json = true;
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
244
|
-
if (!arg?.startsWith?.("--")) {
|
|
245
|
-
positional.push(arg);
|
|
246
|
-
continue;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const eqIndex = arg.indexOf("=");
|
|
250
|
-
const rawName = eqIndex >= 0 ? arg.slice(0, eqIndex) : arg;
|
|
251
|
-
const name = optionName(rawName);
|
|
252
|
-
const mapped = OPTION_KEYS[name];
|
|
253
|
-
if (!mapped) {
|
|
254
|
-
throw new Error(`unknown tfx cto event option: --${name}`);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
let value = eqIndex >= 0 ? arg.slice(eqIndex + 1) : null;
|
|
258
|
-
if (value === null) {
|
|
259
|
-
const next = args[i + 1];
|
|
260
|
-
if (typeof next === "string" && !next.startsWith("--")) {
|
|
261
|
-
value = next;
|
|
262
|
-
i += 1;
|
|
263
|
-
} else {
|
|
264
|
-
value = "true";
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
putPathValue(fields, mapped, value);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return {
|
|
271
|
-
presetName: positional[0] || null,
|
|
272
|
-
extraPositionals: positional.slice(1),
|
|
273
|
-
fields,
|
|
274
|
-
json,
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function mergeActor(defaultActor, overrideActor) {
|
|
279
|
-
const actor = { ...(defaultActor || {}), ...(overrideActor || {}) };
|
|
280
|
-
return Object.keys(actor).length > 0 ? actor : undefined;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function defaultEventSummary(input) {
|
|
284
|
-
if (input.event === "checkpoint_saved") {
|
|
285
|
-
return `context-save checkpoint ${input.checkpoint_id}`;
|
|
286
|
-
}
|
|
287
|
-
if (input.event === "checkpoint_restored") {
|
|
288
|
-
return `context-restore checkpoint ${input.checkpoint_id}`;
|
|
289
|
-
}
|
|
290
|
-
if (input.event === "pr_created") {
|
|
291
|
-
const [pr] = normalizeNumericRefs(input.pr_refs);
|
|
292
|
-
return pr ? `PR #${pr} created` : "PR created";
|
|
293
|
-
}
|
|
294
|
-
if (input.event === "pr_merged_or_closed") {
|
|
295
|
-
const [pr] = normalizeNumericRefs(input.pr_refs);
|
|
296
|
-
return pr ? `PR #${pr} merged or closed` : "PR merged or closed";
|
|
297
|
-
}
|
|
298
|
-
return undefined;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function validateRunEventInput(input) {
|
|
302
|
-
if (!input.event) {
|
|
303
|
-
throw new Error("tfx cto event requires a preset or --event <event_type>");
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
if (
|
|
307
|
-
["checkpoint_saved", "checkpoint_restored"].includes(input.event) &&
|
|
308
|
-
!maybeString(input.checkpoint_id)
|
|
309
|
-
) {
|
|
310
|
-
throw new Error(`tfx cto event ${input.event} requires --checkpoint-id`);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (
|
|
314
|
-
["pr_created", "pr_merged_or_closed"].includes(input.event) &&
|
|
315
|
-
normalizeNumericRefs(input.pr_refs).length === 0
|
|
316
|
-
) {
|
|
317
|
-
throw new Error(`tfx cto event ${input.event} requires --pr`);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
87
|
function putString(target, key, value) {
|
|
322
88
|
const normalized = maybeString(value);
|
|
323
89
|
if (normalized) target[key] = normalized;
|
|
@@ -341,7 +107,16 @@ export function normalizeCtoEvent(input = {}) {
|
|
|
341
107
|
throw new Error(`invalid CTO status: ${status}`);
|
|
342
108
|
}
|
|
343
109
|
|
|
344
|
-
const ts = toIsoTime(input.now ?? input.ts);
|
|
110
|
+
const ts = toIsoTime(input.now ?? input.ts) || new Date().toISOString();
|
|
111
|
+
if (eventType === "collect") {
|
|
112
|
+
return {
|
|
113
|
+
ts,
|
|
114
|
+
event: eventType,
|
|
115
|
+
source: maybeString(input.source) || "tfx_cto_collect",
|
|
116
|
+
summary: maybeString(input.summary) || eventType,
|
|
117
|
+
ref: input.ref ?? null,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
345
120
|
const ref = {
|
|
346
121
|
schema_version: CTO_EVENT_SCHEMA_VERSION,
|
|
347
122
|
event_type: eventType,
|
|
@@ -416,7 +191,7 @@ export async function appendCtoEvent(lakeRoot, input, opts = {}) {
|
|
|
416
191
|
|
|
417
192
|
if (fd === null) {
|
|
418
193
|
stderr.write(
|
|
419
|
-
"[tfx cto
|
|
194
|
+
"[tfx cto] warning: ledger lock timeout; skipped ledger append\n",
|
|
420
195
|
);
|
|
421
196
|
return { appended: false, event };
|
|
422
197
|
}
|
|
@@ -431,59 +206,3 @@ export async function appendCtoEvent(lakeRoot, input, opts = {}) {
|
|
|
431
206
|
} catch {}
|
|
432
207
|
}
|
|
433
208
|
}
|
|
434
|
-
|
|
435
|
-
export async function runEvent(args = [], opts = {}) {
|
|
436
|
-
const parsed = parseEventArgs(args);
|
|
437
|
-
if (parsed.extraPositionals.length > 0) {
|
|
438
|
-
throw new Error(
|
|
439
|
-
`unexpected tfx cto event argument: ${parsed.extraPositionals[0]}`,
|
|
440
|
-
);
|
|
441
|
-
}
|
|
442
|
-
const preset = parsed.presetName ? EVENT_PRESETS[parsed.presetName] : null;
|
|
443
|
-
if (parsed.presetName && !preset && !parsed.fields.event) {
|
|
444
|
-
throw new Error(`unknown tfx cto event preset: ${parsed.presetName}`);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
const rootDir = opts.rootDir || resolveLakeRootDir(process.cwd());
|
|
448
|
-
const lakeRoot = opts.lakeRoot || join(rootDir, ".triflux", "lake");
|
|
449
|
-
const stdout = opts.stdout || process.stdout;
|
|
450
|
-
const jsonOut = opts.json === true || parsed.json;
|
|
451
|
-
const ownerSurface =
|
|
452
|
-
parsed.fields.owner_surface ||
|
|
453
|
-
preset?.ownerSurface ||
|
|
454
|
-
"external wrapper -> tfx cto event --event";
|
|
455
|
-
|
|
456
|
-
const input = {
|
|
457
|
-
...(preset || {}),
|
|
458
|
-
...parsed.fields,
|
|
459
|
-
actor: mergeActor(preset?.actor, parsed.fields.actor),
|
|
460
|
-
};
|
|
461
|
-
delete input.ownerSurface;
|
|
462
|
-
delete input.owner_surface;
|
|
463
|
-
if (!input.project_root) input.project_root = rootDir;
|
|
464
|
-
if (!input.summary) input.summary = defaultEventSummary(input);
|
|
465
|
-
|
|
466
|
-
validateRunEventInput(input);
|
|
467
|
-
|
|
468
|
-
const result = await appendCtoEvent(lakeRoot, input, {
|
|
469
|
-
stderr: opts.stderr,
|
|
470
|
-
lockRetries: opts.ledgerLockRetries,
|
|
471
|
-
lockRetryDelayMs: opts.ledgerLockRetryDelayMs,
|
|
472
|
-
});
|
|
473
|
-
const payload = {
|
|
474
|
-
appended: result.appended,
|
|
475
|
-
owner_surface: ownerSurface,
|
|
476
|
-
event: result.event,
|
|
477
|
-
};
|
|
478
|
-
|
|
479
|
-
if (jsonOut) {
|
|
480
|
-
stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
481
|
-
} else {
|
|
482
|
-
const status = result.appended ? "appended" : "skipped";
|
|
483
|
-
stdout.write(
|
|
484
|
-
`[tfx cto event] ${status} ${result.event.event} via ${ownerSurface}\n`,
|
|
485
|
-
);
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
return payload;
|
|
489
|
-
}
|