@zq-silk/yui 0.6.0 → 0.6.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/README.md +5 -5
- package/dist/agent/managedRuntimeEnvironment.js +2 -1
- package/dist/cli/commandCatalog.js +251 -13
- package/dist/cli/updateOrchestrator.js +8 -0
- package/dist/cli/updatePorts.js +76 -22
- package/dist/cli.js +264 -20
- package/dist/commands/configCommands.js +83 -9
- package/dist/commands/controllerCommands.js +103 -0
- package/dist/commands/deliveryGuardPreflight.js +30 -0
- package/dist/commands/durableJobCommands.js +231 -0
- package/dist/commands/executionAuditCommands.js +193 -0
- package/dist/commands/grantCommands.js +374 -0
- package/dist/commands/projectCommands.js +119 -81
- package/dist/commands/releaseCommands.js +444 -0
- package/dist/commands/resourcesCommands.js +274 -0
- package/dist/commands/sessionCommands.js +104 -0
- package/dist/commands/taskActor.js +117 -0
- package/dist/commands/taskChangeSetCommands.js +60 -0
- package/dist/commands/taskCommands.js +618 -202
- package/dist/commands/taskCompletionGate.js +78 -1
- package/dist/commands/taskContextCommand.js +33 -6
- package/dist/commands/taskInputCommands.js +1 -1
- package/dist/commands/taskIntegrationCommands.js +136 -33
- package/dist/commands/taskIntegrationQueueCommands.js +228 -0
- package/dist/commands/taskNextActionCommand.js +100 -0
- package/dist/commands/taskOverlapCommands.js +120 -0
- package/dist/commands/taskOverviewCommand.js +36 -8
- package/dist/commands/telemetryCommands.js +330 -0
- package/dist/commands/workflowCommands.js +415 -0
- package/dist/config/yuiConfig.js +62 -0
- package/dist/controller/clientRuntime.js +42 -1
- package/dist/controller/controller.js +402 -56
- package/dist/controller/controllerMain.js +25 -2
- package/dist/controller/domainIdentity.js +16 -8
- package/dist/controller/fileSchedulerStoreAdapter.js +423 -31
- package/dist/controller/handoverCandidate.js +168 -0
- package/dist/controller/jobClient.js +102 -0
- package/dist/controller/jobControl.js +613 -0
- package/dist/controller/jobSupervisor.js +498 -0
- package/dist/controller/providerHookRunFence.js +34 -5
- package/dist/controller/resourceCleanupLinux.js +18 -9
- package/dist/controller/resourceInventoryLinux.js +90 -39
- package/dist/controller/runtime.js +165 -15
- package/dist/controller/runtimeEventInbox.js +234 -57
- package/dist/controller/runtimeEventProcessor.js +297 -58
- package/dist/controller/sessionOwnerReconciliation.js +321 -0
- package/dist/core/controllerServer.js +416 -27
- package/dist/core/controllerTelemetry.js +167 -0
- package/dist/doctor/doctor.js +113 -16
- package/dist/domain/validation.js +9 -0
- package/dist/execution/executionGroup.js +40 -3
- package/dist/executor/agentExecutor.js +6 -3
- package/dist/executor/effectiveLaunch.js +52 -0
- package/dist/executor/executorRegistry.js +50 -0
- package/dist/executor/fileRoleLaunchPlanner.js +61 -6
- package/dist/grant/capabilityGrant.js +282 -0
- package/dist/integration/changeSet.js +16 -3
- package/dist/integration/changeSetManifest.js +46 -0
- package/dist/integration/gitIntegrationService.js +528 -147
- package/dist/integration/integrationAttempt.js +54 -5
- package/dist/integration/integrationQueueEntry.js +221 -0
- package/dist/integration/integrationQueueService.js +955 -0
- package/dist/integration/manifestTags.js +99 -0
- package/dist/integration/overlapDiagnostics.js +211 -0
- package/dist/job/durableJob.js +449 -0
- package/dist/job/jobRunner.js +350 -0
- package/dist/lifecycle/exactRunTerminalization.js +24 -2
- package/dist/lifecycle/providerErrorClass.js +126 -0
- package/dist/message/message.js +16 -3
- package/dist/observability/executionAudit.js +545 -0
- package/dist/observability/faultClassification.js +160 -0
- package/dist/observability/runtimeIdentity.js +367 -0
- package/dist/release/fakeReleasePorts.js +55 -0
- package/dist/release/releaseHandover.js +475 -0
- package/dist/release/releaseIdempotencyStore.js +165 -0
- package/dist/release/releaseWorkflow.js +459 -0
- package/dist/release/releaseWorkflowEngine.js +688 -0
- package/dist/release/releaseWorkflowPorts.js +1720 -0
- package/dist/release/runtimeRelease.js +495 -0
- package/dist/release/workflowFileLock.js +218 -0
- package/dist/repository/gitWorkspace.js +177 -1
- package/dist/repository/projectMaintenanceLock.js +315 -0
- package/dist/repository/taskWorkspaceCoordinator.js +87 -17
- package/dist/repository/taskWorkspacePreparer.js +1091 -517
- package/dist/resources/autoResourceGc.js +116 -0
- package/dist/resources/liveReferences.js +574 -0
- package/dist/resources/resourceDiscovery.js +477 -0
- package/dist/resources/resourceGc.js +645 -0
- package/dist/resources/resourceRegistrar.js +256 -0
- package/dist/resources/resourceRegistry.js +150 -0
- package/dist/resources/resourceRegistryStore.js +41 -0
- package/dist/resources/resourceTypes.js +42 -0
- package/dist/resources/sqliteResourceRegistry.js +111 -0
- package/dist/review/reviewConfig.js +10 -0
- package/dist/review/reviewFinding.js +240 -0
- package/dist/review/reviewFindingLedger.js +545 -0
- package/dist/review/reviewOutcomeClassifier.js +61 -0
- package/dist/review/reviewRound.js +56 -4
- package/dist/run/agentRun.js +80 -4
- package/dist/run/providerRetry.js +84 -0
- package/dist/run/providerRetryConfig.js +63 -0
- package/dist/run/yieldReceipt.js +65 -0
- package/dist/runtime/exactControlPlane.js +79 -2
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/sessionOwnerIdentity.js +269 -0
- package/dist/runtime/sessionOwnerRegistry.js +132 -0
- package/dist/runtime/sessionReconciliation.js +93 -0
- package/dist/runtime/sessionTerminationGuard.js +211 -0
- package/dist/runtime/taskRuntimeIsolation.js +13 -0
- package/dist/runtime/tmuxAdapters.js +34 -1
- package/dist/scheduler/actionability.js +155 -0
- package/dist/scheduler/activeRoleRunDelivery.js +14 -5
- package/dist/scheduler/activeTaskProgress.js +60 -0
- package/dist/scheduler/leaderWakeupProcessor.js +22 -11
- package/dist/scheduler/roleRunStall.js +135 -29
- package/dist/scheduler/taskExecutionProjection.js +11 -0
- package/dist/storage/compatibleTaskStore.js +112 -5
- package/dist/storage/migration/productionRegistry.js +736 -1
- package/dist/storage/sqliteSchema.js +264 -3
- package/dist/storage/sqliteStore.js +487 -13
- package/dist/storage/storeRpc.js +21 -0
- package/dist/storage/taskStore.js +974 -21
- package/dist/storage/upgrade/homeClassification.js +120 -2
- package/dist/storage/upgrade/migrationReceipt.js +67 -0
- package/dist/storage/upgrade/pseudoLayoutRepair.js +241 -0
- package/dist/storage/upgrade/recordVersions.js +10 -1
- package/dist/storage/upgrade/sqliteMigrationTarget.js +58 -6
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +290 -0
- package/dist/storage/upgrade/sqliteStateMigration.js +258 -2
- package/dist/storage/upgrade/upgradeOrchestrator.js +482 -16
- package/dist/task/deliveryGuard.js +226 -0
- package/dist/task/nextAction.js +738 -0
- package/dist/task/repairWave.js +137 -0
- package/dist/task/taskRecordReference.js +6 -1
- package/dist/telemetry/sqliteTelemetryStore.js +387 -0
- package/dist/telemetry/telemetryCompaction.js +251 -0
- package/dist/telemetry/telemetryConfig.js +64 -0
- package/dist/telemetry/telemetryRouter.js +32 -0
- package/dist/telemetry/telemetryStore.js +19 -0
- package/dist/telemetry/telemetryWiring.js +33 -0
- package/dist/tmux/tmuxManager.js +20 -1
- package/dist/tmux/tmuxSocketEndpoint.js +20 -0
- package/dist/verification/gateArtifact.js +216 -0
- package/dist/verification/gateArtifactStore.js +87 -0
- package/dist/verification/verificationGateService.js +414 -0
- package/dist/verification/verificationPlan.js +308 -0
- package/dist/workspace/gitChangeSetCapture.js +12 -2
- package/dist/workspace/workItemChangeSetManager.js +60 -3
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +8 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { usageError } from "../errors/cliError.js";
|
|
4
|
+
import { STORAGE_SCHEMA_FILE } from "../storage/storageSchema.js";
|
|
5
|
+
import { openTaskStore, SqliteTaskStore } from "../storage/sqliteStore.js";
|
|
6
|
+
import { COMMITTED_DATABASE_FILENAME } from "../storage/upgrade/sqliteStateMigration.js";
|
|
7
|
+
import { DEFAULT_RUN_CAP, DEFAULT_TERMINAL_KEEP, resolveRunCap, resolveTelemetryMode, resolveTerminalKeep } from "../telemetry/telemetryConfig.js";
|
|
8
|
+
import { applyTelemetryCompaction, planTelemetryCompaction } from "../telemetry/telemetryCompaction.js";
|
|
9
|
+
import { SqliteTelemetryStore } from "../telemetry/sqliteTelemetryStore.js";
|
|
10
|
+
export async function runTelemetryCommand(args, options) {
|
|
11
|
+
const [command, ...rest] = args;
|
|
12
|
+
if (command === "status")
|
|
13
|
+
return telemetryStatus(rest, options);
|
|
14
|
+
if (command === "prune")
|
|
15
|
+
return telemetryPrune(rest, options);
|
|
16
|
+
if (command === "compact")
|
|
17
|
+
return telemetryCompact(rest, options);
|
|
18
|
+
if (command === "read")
|
|
19
|
+
return telemetryRead(rest, options);
|
|
20
|
+
throw usageError(command === undefined
|
|
21
|
+
? "Telemetry command is required: status, prune, compact, or read."
|
|
22
|
+
: `Unknown command: telemetry ${command}`, "yui telemetry status [--json]\n"
|
|
23
|
+
+ "yui telemetry prune [--task <id>] [--keep <n>] [--dry-run] [--json]\n"
|
|
24
|
+
+ "yui telemetry compact --from <home> --staged <dir> [--keep <n>] [--dry-run] [--json]\n"
|
|
25
|
+
+ "yui telemetry read --task <id> [--run <id>] [--aggregate] [--limit <n>] [--offset <n>]");
|
|
26
|
+
}
|
|
27
|
+
// -- status ---------------------------------------------------------------------
|
|
28
|
+
function telemetryStatus(args, options) {
|
|
29
|
+
const flags = parseFlags(args, new Set([]));
|
|
30
|
+
const env = options.environment ?? process.env;
|
|
31
|
+
const mode = resolveTelemetryMode(env);
|
|
32
|
+
const telemetry = new SqliteTelemetryStore(options.home, {
|
|
33
|
+
mode,
|
|
34
|
+
terminalKeep: resolveTerminalKeep(env),
|
|
35
|
+
runCap: resolveRunCap(env)
|
|
36
|
+
});
|
|
37
|
+
try {
|
|
38
|
+
const health = telemetry.health();
|
|
39
|
+
const store = options.store;
|
|
40
|
+
const perTask = store === undefined
|
|
41
|
+
? []
|
|
42
|
+
: store.listTasks().map((task) => ({
|
|
43
|
+
taskId: task.id,
|
|
44
|
+
rows: telemetry.count(task.id),
|
|
45
|
+
runs: telemetry.listRunAggregates(task.id).length
|
|
46
|
+
}));
|
|
47
|
+
const report = {
|
|
48
|
+
mode,
|
|
49
|
+
database: join(options.home, COMMITTED_DATABASE_FILENAME),
|
|
50
|
+
available: health.available,
|
|
51
|
+
dropped: health.dropped,
|
|
52
|
+
coalesced: health.coalesced,
|
|
53
|
+
lastError: health.lastError,
|
|
54
|
+
totalRows: health.rows,
|
|
55
|
+
terminalKeep: resolveTerminalKeep(env),
|
|
56
|
+
runCap: resolveRunCap(env),
|
|
57
|
+
tasks: perTask
|
|
58
|
+
};
|
|
59
|
+
if (options.json || flags.has("json"))
|
|
60
|
+
return JSON.stringify(report, null, 2);
|
|
61
|
+
const lines = [
|
|
62
|
+
`Mode: ${mode}`,
|
|
63
|
+
`Database: ${report.database}`,
|
|
64
|
+
`Available: ${health.available ? "yes" : "no"}`,
|
|
65
|
+
`Rows: ${health.rows}`,
|
|
66
|
+
`Dropped: ${health.dropped}`,
|
|
67
|
+
`Coalesced: ${health.coalesced}`,
|
|
68
|
+
...(health.lastError === null ? [] : [`Last error: ${health.lastError}`]),
|
|
69
|
+
`Terminal keep: ${report.terminalKeep}`,
|
|
70
|
+
`Run cap: ${report.runCap}`
|
|
71
|
+
];
|
|
72
|
+
for (const task of perTask) {
|
|
73
|
+
lines.push(` ${task.taskId}: ${task.rows} rows across ${task.runs} run generation(s)`);
|
|
74
|
+
}
|
|
75
|
+
return lines.join("\n");
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
void telemetry.close();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// -- prune ----------------------------------------------------------------------
|
|
82
|
+
function telemetryPrune(args, options) {
|
|
83
|
+
const flags = parseFlags(args, new Set(["dry-run", "json"]));
|
|
84
|
+
const taskId = stringOption(args, "--task");
|
|
85
|
+
const keep = integerOption(args, "--keep", DEFAULT_TERMINAL_KEEP);
|
|
86
|
+
const dryRun = flags.has("dry-run");
|
|
87
|
+
const env = options.environment ?? process.env;
|
|
88
|
+
const cap = resolveRunCap(env);
|
|
89
|
+
const store = requireStore(options);
|
|
90
|
+
const telemetry = new SqliteTelemetryStore(options.home, {
|
|
91
|
+
mode: resolveTelemetryMode(env),
|
|
92
|
+
terminalKeep: keep,
|
|
93
|
+
runCap: cap
|
|
94
|
+
});
|
|
95
|
+
try {
|
|
96
|
+
const tasks = taskId === undefined
|
|
97
|
+
? store.listTasks().map((task) => task.id)
|
|
98
|
+
: [taskId];
|
|
99
|
+
const report = [];
|
|
100
|
+
for (const id of tasks) {
|
|
101
|
+
const terminalRuns = new Set(store.listAgentRuns(id)
|
|
102
|
+
.filter((run) => run.status !== "active")
|
|
103
|
+
.map((run) => run.id));
|
|
104
|
+
const entry = { taskId: id, terminalPruned: 0, activeCapped: 0, generations: [] };
|
|
105
|
+
for (const aggregate of telemetry.listRunAggregates(id)) {
|
|
106
|
+
if (terminalRuns.has(aggregate.runId)) {
|
|
107
|
+
const before = telemetry.count(id, aggregate.runId);
|
|
108
|
+
const deleted = dryRun
|
|
109
|
+
? Math.max(0, before - keep)
|
|
110
|
+
: telemetry.pruneGeneration(aggregate.taskId, aggregate.roleName, aggregate.runId, aggregate.generation, keep);
|
|
111
|
+
entry.terminalPruned += deleted;
|
|
112
|
+
entry.generations.push({
|
|
113
|
+
runId: aggregate.runId,
|
|
114
|
+
generation: aggregate.generation,
|
|
115
|
+
kept: Math.min(before, keep),
|
|
116
|
+
deleted
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
for (const run of store.listAgentRuns(id)) {
|
|
121
|
+
if (run.status !== "active")
|
|
122
|
+
continue;
|
|
123
|
+
const before = telemetry.count(id, run.id);
|
|
124
|
+
if (before <= cap)
|
|
125
|
+
continue;
|
|
126
|
+
const deleted = dryRun ? before - cap : telemetry.capRun(id, run.id, cap);
|
|
127
|
+
entry.activeCapped += deleted;
|
|
128
|
+
entry.generations.push({
|
|
129
|
+
runId: run.id,
|
|
130
|
+
generation: "*",
|
|
131
|
+
kept: Math.min(before, cap),
|
|
132
|
+
deleted
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
report.push(entry);
|
|
136
|
+
}
|
|
137
|
+
const totals = report.reduce((acc, entry) => ({
|
|
138
|
+
terminalPruned: acc.terminalPruned + entry.terminalPruned,
|
|
139
|
+
activeCapped: acc.activeCapped + entry.activeCapped
|
|
140
|
+
}), { terminalPruned: 0, activeCapped: 0 });
|
|
141
|
+
const result = { dryRun, keep, cap, totals, tasks: report };
|
|
142
|
+
if (options.json || flags.has("json"))
|
|
143
|
+
return JSON.stringify(result, null, 2);
|
|
144
|
+
const lines = [
|
|
145
|
+
`Telemetry prune (${dryRun ? "dry-run" : "applied"}):`,
|
|
146
|
+
` terminal generations pruned: ${totals.terminalPruned} row(s) (keep ${keep})`,
|
|
147
|
+
` active runs capped: ${totals.activeCapped} row(s) (cap ${cap})`
|
|
148
|
+
];
|
|
149
|
+
for (const entry of report) {
|
|
150
|
+
if (entry.generations.length === 0)
|
|
151
|
+
continue;
|
|
152
|
+
lines.push(` ${entry.taskId}:`);
|
|
153
|
+
for (const generation of entry.generations) {
|
|
154
|
+
lines.push(` ${generation.runId}/${generation.generation}: kept ${generation.kept}, deleted ${generation.deleted}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return lines.join("\n");
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
void telemetry.close();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// -- compact --------------------------------------------------------------------
|
|
164
|
+
function telemetryCompact(args, options) {
|
|
165
|
+
const flags = parseFlags(args, new Set(["dry-run", "json"]));
|
|
166
|
+
const from = stringOption(args, "--from");
|
|
167
|
+
const staged = stringOption(args, "--staged");
|
|
168
|
+
const keep = integerOption(args, "--keep", DEFAULT_TERMINAL_KEEP);
|
|
169
|
+
const dryRun = flags.has("dry-run");
|
|
170
|
+
if (from === undefined || staged === undefined) {
|
|
171
|
+
throw usageError("telemetry compact requires --from <home> and --staged <dir>.", "yui telemetry compact --from <home> --staged <dir> [--keep <n>] [--dry-run]");
|
|
172
|
+
}
|
|
173
|
+
const resolvedFrom = resolvePath(from);
|
|
174
|
+
const resolvedStaged = resolvePath(staged);
|
|
175
|
+
if (resolvedFrom === resolvedStaged) {
|
|
176
|
+
throw usageError("--from and --staged must be different directories.");
|
|
177
|
+
}
|
|
178
|
+
if (!existsSync(join(resolvedFrom, STORAGE_SCHEMA_FILE))) {
|
|
179
|
+
throw usageError(`--from is not a Yui Home (missing ${STORAGE_SCHEMA_FILE}): ${resolvedFrom}`);
|
|
180
|
+
}
|
|
181
|
+
if (existsSync(resolvedStaged) && dryRun === false) {
|
|
182
|
+
throw usageError(`--staged already exists; refuse to overwrite: ${resolvedStaged}`);
|
|
183
|
+
}
|
|
184
|
+
// Database-only direction: compaction folds semantic progress into the
|
|
185
|
+
// telemetry tables inside `yui.db`. A Home without a database has not
|
|
186
|
+
// reached SQLite storage yet; migrate it first (yui upgrade) instead
|
|
187
|
+
// of silently compacting a file-only copy that telemetry cannot join.
|
|
188
|
+
if (!existsSync(join(resolvedFrom, COMMITTED_DATABASE_FILENAME))) {
|
|
189
|
+
throw usageError(`--from is not a SQLite Home (missing ${COMMITTED_DATABASE_FILENAME}): ${resolvedFrom}. `
|
|
190
|
+
+ "Migrate it to the database backend before compaction.");
|
|
191
|
+
}
|
|
192
|
+
const backend = "sqlite";
|
|
193
|
+
mkdirSync(resolvedStaged, { recursive: true });
|
|
194
|
+
copyStoreFiles(resolvedFrom, resolvedStaged);
|
|
195
|
+
const store = openTaskStore(resolvedStaged, backend);
|
|
196
|
+
const telemetry = new SqliteTelemetryStore(resolvedStaged, {
|
|
197
|
+
mode: "bounded",
|
|
198
|
+
terminalKeep: keep,
|
|
199
|
+
runCap: DEFAULT_RUN_CAP
|
|
200
|
+
});
|
|
201
|
+
try {
|
|
202
|
+
const plan = planTelemetryCompaction(store, { terminalKeep: keep });
|
|
203
|
+
const receipt = applyTelemetryCompaction(store, telemetry, plan, {
|
|
204
|
+
dryRun,
|
|
205
|
+
source: resolvedFrom,
|
|
206
|
+
terminalKeep: keep
|
|
207
|
+
});
|
|
208
|
+
writeFileSync(join(resolvedStaged, "telemetry-compaction.json"), JSON.stringify(receipt, null, 2) + "\n");
|
|
209
|
+
if (options.json || flags.has("json"))
|
|
210
|
+
return JSON.stringify({ staged: resolvedStaged, receipt }, null, 2);
|
|
211
|
+
return [
|
|
212
|
+
`Telemetry compaction (${dryRun ? "dry-run" : "applied"}) on staged copy:`,
|
|
213
|
+
` staged: ${resolvedStaged}`,
|
|
214
|
+
` backend: ${backend}`,
|
|
215
|
+
` tasks: ${receipt.totals.tasks}`,
|
|
216
|
+
` progress: ${receipt.totals.progressEvents} event(s) across ${receipt.totals.generations} generation(s)`,
|
|
217
|
+
` sidecar rows: ${receipt.totals.telemetryRows} (window ${keep}/generation)`,
|
|
218
|
+
` receipt: ${join(resolvedStaged, "telemetry-compaction.json")}`,
|
|
219
|
+
dryRun
|
|
220
|
+
? "Dry-run made no changes. Re-run without --dry-run, then promote the staged Home after verifying the receipt."
|
|
221
|
+
: "Applied. Promote the staged Home only after verifying the receipt; the original Home is unchanged."
|
|
222
|
+
].join("\n");
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
void telemetry.close();
|
|
226
|
+
if (store instanceof SqliteTaskStore)
|
|
227
|
+
store.close();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// -- read -----------------------------------------------------------------------
|
|
231
|
+
function telemetryRead(args, options) {
|
|
232
|
+
const flags = parseFlags(args, new Set(["aggregate", "json"]));
|
|
233
|
+
const taskId = stringOption(args, "--task");
|
|
234
|
+
if (taskId === undefined) {
|
|
235
|
+
throw usageError("telemetry read requires --task <id>.", "yui telemetry read --task <id> [--run <id>] [--aggregate]");
|
|
236
|
+
}
|
|
237
|
+
const runId = stringOption(args, "--run");
|
|
238
|
+
const limit = integerOption(args, "--limit", 100);
|
|
239
|
+
const offset = integerOption(args, "--offset", 0);
|
|
240
|
+
const env = options.environment ?? process.env;
|
|
241
|
+
const telemetry = new SqliteTelemetryStore(options.home, {
|
|
242
|
+
mode: resolveTelemetryMode(env),
|
|
243
|
+
terminalKeep: resolveTerminalKeep(env),
|
|
244
|
+
runCap: resolveRunCap(env)
|
|
245
|
+
});
|
|
246
|
+
try {
|
|
247
|
+
if (flags.has("aggregate")) {
|
|
248
|
+
if (runId === undefined) {
|
|
249
|
+
const aggregates = telemetry.listRunAggregates(taskId);
|
|
250
|
+
if (options.json || flags.has("json"))
|
|
251
|
+
return JSON.stringify(aggregates, null, 2);
|
|
252
|
+
return aggregates.map((aggregate) => formatAggregate(aggregate)).join("\n") || "(no telemetry)";
|
|
253
|
+
}
|
|
254
|
+
const aggregate = telemetry.aggregate(taskId, runId);
|
|
255
|
+
if (options.json || flags.has("json"))
|
|
256
|
+
return JSON.stringify(aggregate, null, 2);
|
|
257
|
+
return aggregate === null ? "(no telemetry for this Run)" : formatAggregate(aggregate);
|
|
258
|
+
}
|
|
259
|
+
const page = telemetry.list(taskId, runId, { limit, offset });
|
|
260
|
+
if (options.json || flags.has("json"))
|
|
261
|
+
return JSON.stringify(page, null, 2);
|
|
262
|
+
const lines = page.items.map((entry) => `${entry.receivedAt} ${entry.runId}/${entry.generation}/${entry.progressId}`
|
|
263
|
+
+ `${entry.sequence === undefined ? "" : ` seq=${entry.sequence}`}`);
|
|
264
|
+
if (page.nextOffset !== null)
|
|
265
|
+
lines.push(`(next offset: ${page.nextOffset})`);
|
|
266
|
+
return lines.join("\n") || "(no telemetry rows)";
|
|
267
|
+
}
|
|
268
|
+
finally {
|
|
269
|
+
void telemetry.close();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// -- helpers --------------------------------------------------------------------
|
|
273
|
+
function requireStore(options) {
|
|
274
|
+
if (options.store === undefined) {
|
|
275
|
+
throw usageError("This telemetry command requires the Home store.");
|
|
276
|
+
}
|
|
277
|
+
return options.store;
|
|
278
|
+
}
|
|
279
|
+
function resolvePath(value) {
|
|
280
|
+
return value.startsWith("/") ? value : join(process.cwd(), value);
|
|
281
|
+
}
|
|
282
|
+
function copyStoreFiles(from, staged) {
|
|
283
|
+
for (const filename of [
|
|
284
|
+
COMMITTED_DATABASE_FILENAME,
|
|
285
|
+
`${COMMITTED_DATABASE_FILENAME}-wal`,
|
|
286
|
+
`${COMMITTED_DATABASE_FILENAME}-shm`
|
|
287
|
+
]) {
|
|
288
|
+
const source = join(from, filename);
|
|
289
|
+
if (existsSync(source))
|
|
290
|
+
copyFile(source, join(staged, filename));
|
|
291
|
+
}
|
|
292
|
+
copyFile(join(from, STORAGE_SCHEMA_FILE), join(staged, STORAGE_SCHEMA_FILE));
|
|
293
|
+
}
|
|
294
|
+
function copyFile(source, destination) {
|
|
295
|
+
writeFileSync(destination, readFileSync(source));
|
|
296
|
+
}
|
|
297
|
+
function parseFlags(args, known) {
|
|
298
|
+
const flags = new Set();
|
|
299
|
+
for (const arg of args) {
|
|
300
|
+
if (!arg.startsWith("--"))
|
|
301
|
+
continue;
|
|
302
|
+
const name = arg.split("=", 1)[0].replace(/^--/, "");
|
|
303
|
+
if (known.has(name))
|
|
304
|
+
flags.add(name);
|
|
305
|
+
}
|
|
306
|
+
return flags;
|
|
307
|
+
}
|
|
308
|
+
function stringOption(args, name) {
|
|
309
|
+
const index = args.indexOf(name);
|
|
310
|
+
if (index === -1)
|
|
311
|
+
return undefined;
|
|
312
|
+
const value = args[index + 1];
|
|
313
|
+
if (value === undefined || value.startsWith("--")) {
|
|
314
|
+
throw usageError(`${name} requires a value.`);
|
|
315
|
+
}
|
|
316
|
+
return value;
|
|
317
|
+
}
|
|
318
|
+
function integerOption(args, name, fallback) {
|
|
319
|
+
const raw = stringOption(args, name);
|
|
320
|
+
if (raw === undefined)
|
|
321
|
+
return fallback;
|
|
322
|
+
const value = Number(raw);
|
|
323
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
324
|
+
throw usageError(`${name} must be a positive integer; got ${JSON.stringify(raw)}.`);
|
|
325
|
+
}
|
|
326
|
+
return value;
|
|
327
|
+
}
|
|
328
|
+
function formatAggregate(aggregate) {
|
|
329
|
+
return `${aggregate.runId}/${aggregate.generation}: count=${aggregate.count} first=${aggregate.firstAt} last=${aggregate.lastAt} maxSequence=${aggregate.maxSequence === null ? "-" : aggregate.maxSequence} errors=${aggregate.errorCount}`;
|
|
330
|
+
}
|