@zq-silk/yui 0.15.7 → 0.15.8
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/ARCHITECTURE.md +192 -399
- package/README.md +127 -1149
- package/dist/agent/adapterCatalog.js +15 -2
- package/dist/agent/agent.js +23 -3
- package/dist/agent/argumentPolicy.js +7 -1
- package/dist/agent/connectionPlan.js +62 -0
- package/dist/agent/executionComponents.js +158 -0
- package/dist/agent/launchEnvironment.js +31 -3
- package/dist/agent/managedRuntimeEnvironment.js +3 -5
- package/dist/{turn/turn.js → agentRun/agentRun.js} +166 -109
- package/dist/{turn/turnIdentity.js → agentRun/runIdentity.js} +4 -4
- package/dist/brief/taskBrief.js +12 -0
- package/dist/cli/agentConfigurationPicker.js +13 -0
- package/dist/cli/commandCatalog.js +157 -70
- package/dist/cli/interactionCandidates.js +5 -5
- package/dist/cli/interactionPolicy.js +38 -8
- package/dist/cli/invocationRouter.js +1 -1
- package/dist/cli/managedDiagnostics.js +28 -0
- package/dist/cli/operatorWizard.js +1 -7
- package/dist/cli/roleOptionOrder.js +27 -0
- package/dist/cli/roleWizard.js +50 -14
- package/dist/cli/updateOrchestrator.js +1 -1
- package/dist/cli/updatePorts.js +3 -4
- package/dist/cli.js +178 -95
- package/dist/commands/agentCommands.js +72 -14
- package/dist/commands/capabilityCommands.js +9 -6
- package/dist/commands/configCommands.js +20 -20
- package/dist/commands/deliveryGuardPreflight.js +2 -2
- package/dist/commands/executionAuditCommands.js +24 -24
- package/dist/commands/globalRoleCommands.js +1 -1
- package/dist/commands/grantCommands.js +4 -4
- package/dist/commands/operatorCommands.js +1 -7
- package/dist/commands/projectCommands.js +4 -4
- package/dist/commands/resourcesCommands.js +2 -2
- package/dist/commands/roleConfiguration.js +25 -5
- package/dist/commands/roleRuntimeGuard.js +4 -5
- package/dist/commands/sessionCommands.js +3 -7
- package/dist/commands/taskActivationCommands.js +259 -0
- package/dist/commands/taskActor.js +28 -49
- package/dist/commands/taskCommands.js +1188 -710
- package/dist/commands/taskContextCommand.js +39 -583
- package/dist/commands/taskExecutionCommands.js +32 -32
- package/dist/commands/taskInputCommands.js +40 -104
- package/dist/commands/taskIntegrationCommands.js +3 -2
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskNextActionCommand.js +8 -8
- package/dist/commands/taskOverviewCommand.js +33 -45
- package/dist/commands/taskRemoteDeliveryCommand.js +2 -2
- package/dist/commands/taskRoleRuntimeStatus.js +133 -102
- package/dist/commands/telemetryCommands.js +36 -38
- package/dist/config/configCatalog.js +4 -4
- package/dist/config/yuiConfig.js +8 -8
- package/dist/context/contextSnapshot.js +10 -10
- package/dist/context/dispatchContext.js +11 -11
- package/dist/context/roleSessionContext.js +6 -3
- package/dist/context/{turnContextPack.js → runContextPack.js} +146 -81
- package/dist/context/{turnInputContract.js → runInputContract.js} +73 -60
- package/dist/context/sessionBootstrapManifest.js +21 -2
- package/dist/context/sourceRunContext.js +30 -0
- package/dist/context/taskContext.js +458 -0
- package/dist/context/wakeNotification.js +27 -27
- package/dist/controller/agentRuntimeObserver.js +21 -24
- package/dist/controller/capabilityBridge.js +17 -6
- package/dist/controller/clientRuntime.js +65 -92
- package/dist/controller/controller.js +114 -188
- package/dist/controller/fileSchedulerStoreAdapter.js +787 -887
- package/dist/controller/jobControl.js +54 -85
- package/dist/controller/resourceInventory.js +8 -27
- package/dist/controller/resourceInventoryLinux.js +12 -13
- package/dist/controller/runtime.js +529 -479
- package/dist/controller/runtimeEventInbox.js +55 -25
- package/dist/controller/runtimeEventProcessor.js +22 -31
- package/dist/controller/{runtimeHookTurnFence.js → runtimeHookRunFence.js} +91 -115
- package/dist/controller/runtimeLaunchCoordinator.js +80 -426
- package/dist/controller/runtimeObservationHook.js +14 -18
- package/dist/controller/sessionNotify.js +16 -24
- package/dist/controller/sessionOwnerReconciliation.js +168 -50
- package/dist/controller/structuredProviderObservation.js +138 -99
- package/dist/coordination/workMailbox.js +3 -3
- package/dist/coordination/workMailboxQueue.js +36 -33
- package/dist/core/boundedRpc.js +8 -1
- package/dist/core/controllerClient.js +20 -1
- package/dist/core/controllerServer.js +4 -4
- package/dist/doctor/doctor.js +13 -2
- package/dist/domain/agentResultTransport.js +9 -9
- package/dist/execution/codexThreadNaming.js +2 -8
- package/dist/execution/executionHealth.js +51 -63
- package/dist/execution/reviewMainRun.js +137 -0
- package/dist/execution/workItemExecution.js +28 -29
- package/dist/execution/workItemExecutionProjection.js +99 -107
- package/dist/execution/workItemMainRun.js +141 -0
- package/dist/executor/agentAdapter.js +227 -20
- package/dist/executor/agentConfigurationCatalog.js +126 -4
- package/dist/executor/agentConfigurationProbe.js +162 -4
- package/dist/executor/agentExecutor.js +79 -78
- package/dist/executor/effectiveLaunch.js +105 -18
- package/dist/executor/executorRegistry.js +29 -44
- package/dist/executor/fileRoleLaunchPlanner.js +229 -154
- package/dist/executor/workspacePreflightClassification.js +16 -16
- package/dist/grant/capabilityGrant.js +6 -3
- package/dist/input/inputRequest.js +12 -10
- package/dist/integration/gitIntegrationService.js +4 -11
- package/dist/integration/integrationQueueService.js +4 -4
- package/dist/interaction/operatorPresentation.js +1 -1
- package/dist/kernel/builtinCapabilities.js +247 -12
- package/dist/kernel/capabilityRegistry.js +64 -18
- package/dist/kernel/instanceHost.js +12 -1
- package/dist/kernel/kernelPorts.js +2 -2
- package/dist/lifecycle/canonicalLifecycleEvent.js +44 -49
- package/dist/lifecycle/exactRunTerminalization.js +449 -0
- package/dist/message/message.js +62 -6
- package/dist/message/messageContinuation.js +204 -0
- package/dist/observability/executionAudit.js +70 -72
- package/dist/observability/faultClassification.js +2 -2
- package/dist/observability/orchestrationMetrics.js +8 -8
- package/dist/operator/operatorSessionHistory.js +1 -7
- package/dist/output/agentConfigurationPresentation.js +8 -3
- package/dist/output/agentRunConfigurationPresentation.js +128 -0
- package/dist/output/rolePresentation.js +54 -3
- package/dist/plugins/pluginChild.js +104 -0
- package/dist/plugins/pluginIntent.js +26 -0
- package/dist/plugins/pluginInterpreter.js +43 -0
- package/dist/plugins/pluginPackage.js +101 -0
- package/dist/plugins/pluginProcess.js +112 -0
- package/dist/plugins/pluginService.js +380 -0
- package/dist/profile/agentProfile.js +1 -1
- package/dist/repository/gitWorkspace.js +26 -4
- package/dist/repository/project.js +19 -4
- package/dist/repository/taskBaseFreshness.js +13 -13
- package/dist/repository/taskWorkspaceCoordinator.js +20 -27
- package/dist/repository/taskWorkspacePreparer.js +344 -83
- package/dist/resources/autoResourceGc.js +3 -3
- package/dist/resources/liveReferences.js +3 -3
- package/dist/resources/projectResource.js +123 -0
- package/dist/resources/projectResourceService.js +421 -0
- package/dist/resources/resourceDiscovery.js +6 -6
- package/dist/resources/resourceGc.js +1 -1
- package/dist/resources/resourceRegistrar.js +1 -1
- package/dist/resources/resourceTypes.js +1 -1
- package/dist/review/deltaRecheck.js +3 -3
- package/dist/review/reviewAcceptance.js +16 -16
- package/dist/review/reviewDecision.js +7 -7
- package/dist/review/reviewRound.js +21 -20
- package/dist/review/reviewerAvailability.js +2 -2
- package/dist/role/role.js +51 -7
- package/dist/role/taskRoleUpdate.js +30 -0
- package/dist/runtime/acpProtocol.js +425 -0
- package/dist/runtime/acpSession.js +731 -0
- package/dist/runtime/acpSessionConfiguration.js +260 -0
- package/dist/runtime/agentDriver.js +30 -11
- package/dist/runtime/agentEndpoint.js +278 -0
- package/dist/runtime/agentEndpointIdentity.js +86 -0
- package/dist/runtime/agentEndpointOwnership.js +239 -0
- package/dist/runtime/agentError.js +2 -10
- package/dist/runtime/agentHost.js +565 -314
- package/dist/runtime/agentRunConfiguration.js +258 -0
- package/dist/runtime/builtinAgentDrivers.js +134 -18
- package/dist/runtime/builtinAgentErrorMappers.js +55 -3
- package/dist/runtime/builtinTranscriptUsage.js +1 -1
- package/dist/runtime/claude-process-owner +0 -0
- package/dist/runtime/codexAppServerRuntime.js +38 -30
- package/dist/runtime/codexInteractiveHost.js +41 -6
- package/dist/runtime/continuationManager.js +2 -6
- package/dist/runtime/executionEnvironment.js +30 -0
- package/dist/runtime/firstProgressAdvisory.js +11 -11
- package/dist/runtime/index.js +4 -3
- package/dist/runtime/jsonLineChannel.js +109 -0
- package/dist/runtime/launchBroker.js +91 -16
- package/dist/runtime/launchDiagnostics.js +2 -2
- package/dist/runtime/lifecycleReservation.js +10 -18
- package/dist/runtime/managedCaller.js +61 -17
- package/dist/runtime/nativeSessionControl.js +102 -0
- package/dist/runtime/ports.js +6 -21
- package/dist/runtime/processExitObservation.js +8 -7
- package/dist/runtime/promptEnvelope.js +17 -6
- package/dist/runtime/providerContinuation.js +3 -9
- package/dist/runtime/providerContinuationReconciliationService.js +4 -13
- package/dist/runtime/providerControl.js +2 -7
- package/dist/runtime/providerRuntimeIdentity.js +110 -222
- package/dist/runtime/providerRuntimeReconciler.js +5 -9
- package/dist/runtime/runtimeBinding.js +0 -1
- package/dist/runtime/runtimeContinuationProjection.js +4 -7
- package/dist/runtime/runtimeDeadlines.js +9 -0
- package/dist/runtime/runtimeHealthPolicy.js +1 -1
- package/dist/runtime/runtimeObservation.js +29 -65
- package/dist/runtime/runtimeProjection.js +43 -51
- package/dist/runtime/runtimeSessionCandidate.js +1 -3
- package/dist/runtime/sessionLaunchRequest.js +3 -7
- package/dist/runtime/sessionOwnerIdentity.js +7 -54
- package/dist/runtime/sessionOwnerRegistry.js +22 -17
- package/dist/runtime/sessionReconciliation.js +4 -8
- package/dist/runtime/sessionTerminationGuard.js +70 -259
- package/dist/runtime/sessionTokenMetrics.js +5 -16
- package/dist/runtime/structuredProviderHost.js +237 -117
- package/dist/runtime/taskRuntimeIsolation.js +39 -122
- package/dist/runtime/tmuxAdapters.js +39 -86
- package/dist/scheduler/activeRoleRunDelivery.js +354 -0
- package/dist/scheduler/leaderWakeupProcessor.js +75 -266
- package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
- package/dist/scheduler/ports.js +80 -9
- package/dist/scheduler/{roleTurnLiveness.js → roleRunLiveness.js} +26 -30
- package/dist/scheduler/{roleTurnStall.js → roleRunStall.js} +128 -139
- package/dist/scheduler/taskExecutionProjection.js +120 -124
- package/dist/scheduler/taskObservabilityProjection.js +29 -29
- package/dist/scheduler/taskWake.js +11 -4
- package/dist/scheduler/wakeReason.js +9 -1
- package/dist/setup/setupCommand.js +1 -0
- package/dist/storage/migrations/agentRunContract.js +159 -0
- package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
- package/dist/storage/sqliteSchema.js +431 -5
- package/dist/storage/sqliteStore.js +375 -220
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +10 -5
- package/dist/storage/taskStore.js +13 -11
- package/dist/storage/upgrade/upgradeOrchestrator.js +5 -7
- package/dist/surface/surfaceContributions.js +102 -0
- package/dist/task/completionReadiness.js +32 -6
- package/dist/task/deliveryGuard.js +16 -16
- package/dist/task/draftPlan.js +72 -12
- package/dist/task/nextAction.js +144 -128
- package/dist/task/remoteDelivery.js +6 -6
- package/dist/task/task.js +184 -18
- package/dist/task/taskActivation.js +301 -0
- package/dist/task/taskActivationService.js +392 -0
- package/dist/task/taskRecordReference.js +5 -4
- package/dist/task/taskRecordRetirement.js +1 -1
- package/dist/telemetry/sqliteTelemetryStore.js +55 -68
- package/dist/telemetry/telemetryConfig.js +14 -14
- package/dist/telemetry/telemetryWiring.js +2 -2
- package/dist/web/assets/assetManifest.js +2 -0
- package/dist/web/assets/client/app.js +120 -20
- package/dist/web/assets/client/components.js +87 -54
- package/dist/web/assets/client/i18n.js +83 -41
- package/dist/web/assets/client/markdown.js +1 -1
- package/dist/web/assets/client/taskSurface.js +353 -0
- package/dist/web/assets/client/view.js +49 -44
- package/dist/web/assets/shell.js +1 -1
- package/dist/web/assets/styles/cards.js +22 -4
- package/dist/web/controllerWeb.js +60 -0
- package/dist/web/webMutation.js +28 -0
- package/dist/web/webServer.js +118 -8
- package/dist/web/webSnapshot.js +81 -74
- package/dist/web/webTaskSurface.js +64 -0
- package/dist/workItem/dependencyGate.js +1 -1
- package/dist/workItem/workItem.js +80 -48
- package/dist/workspace/workItemChangeSetManager.js +16 -9
- package/docs/agent-result-consumption.md +94 -0
- package/docs/agent-runtime-drivers.md +91 -0
- package/docs/architecture/README.md +38 -0
- package/docs/architecture/capabilities-and-resources.md +79 -0
- package/docs/managed-turn-and-session-runtime.md +222 -0
- package/docs/observability/README.md +81 -0
- package/docs/plugin-sdk.md +290 -0
- package/docs/provider-runtime.md +163 -0
- package/docs/release-workflow.md +303 -0
- package/docs/roles-and-configuration.md +113 -0
- package/docs/sqlite-control-plane-design.md +76 -0
- package/docs/task-dag-semantics.md +57 -0
- package/docs/task-delivery.md +103 -0
- package/docs/task-local-identity.md +6 -6
- package/docs/testing/verification-levels.md +86 -0
- package/i18n/README.zh-CN.md +98 -739
- package/package.json +2 -2
- package/skills/yui-leader/SKILL.md +130 -103
- package/skills/yui-leader/references/integration.md +39 -0
- package/skills/yui-leader/references/replicated-execution.md +42 -0
- package/skills/yui-leader/references/task-plugins.md +33 -0
- package/skills/yui-operator/SKILL.md +30 -59
- package/skills/yui-reviewer/SKILL.md +35 -36
- package/skills/yui-runtime/SKILL.md +88 -24
- package/skills/yui-runtime/references/publication.md +22 -0
- package/skills/yui-runtime/references/recovery.md +64 -0
- package/skills/yui-worker/SKILL.md +37 -39
- package/dist/cli/roleOptionCatalog.js +0 -68
- package/dist/context/sourceTurnContext.js +0 -30
- package/dist/execution/reviewMainTurn.js +0 -161
- package/dist/execution/workItemMainTurn.js +0 -164
- package/dist/lifecycle/exactTurnTerminalization.js +0 -407
- package/dist/runtime/preallocatedNativeSession.js +0 -13
- package/dist/runtime/runtimeStopReceipt.js +0 -42
- package/dist/scheduler/activeRoleTurnDelivery.js +0 -315
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import Database from "better-sqlite3";
|
|
4
|
-
import {
|
|
4
|
+
import { DEFAULT_RUN_CAP, DEFAULT_TERMINAL_KEEP } from "./telemetryConfig.js";
|
|
5
5
|
import { migrateSqliteSchema } from "../storage/sqliteSchema.js";
|
|
6
6
|
import { CURRENT_DATABASE_FILENAME as COMMITTED_DATABASE_FILENAME } from "../storage/currentTaskStore.js";
|
|
7
7
|
/**
|
|
@@ -18,7 +18,7 @@ import { CURRENT_DATABASE_FILENAME as COMMITTED_DATABASE_FILENAME } from "../sto
|
|
|
18
18
|
*
|
|
19
19
|
* The telemetry tables are maintained by the centralized schema baseline:
|
|
20
20
|
* `telemetry` holds the bounded latest-per-key window and
|
|
21
|
-
* `telemetry_aggregate` holds the authoritative per-
|
|
21
|
+
* `telemetry_aggregate` holds the authoritative per-AgentRun
|
|
22
22
|
* summary, maintained by triggers so it survives window pruning.
|
|
23
23
|
*/
|
|
24
24
|
const MAX_PAGE_LIMIT = 500;
|
|
@@ -41,7 +41,7 @@ export class SqliteTelemetryStore {
|
|
|
41
41
|
this.mode = options.mode ?? "on";
|
|
42
42
|
this.#path = join(home, COMMITTED_DATABASE_FILENAME);
|
|
43
43
|
this.#terminalKeep = options.terminalKeep ?? DEFAULT_TERMINAL_KEEP;
|
|
44
|
-
this.#turnCap = options.
|
|
44
|
+
this.#turnCap = options.runCap ?? DEFAULT_RUN_CAP;
|
|
45
45
|
this.#maxPending = options.maxPending ?? 10_000;
|
|
46
46
|
}
|
|
47
47
|
// -- TelemetrySink -------------------------------------------------------------
|
|
@@ -89,53 +89,52 @@ export class SqliteTelemetryStore {
|
|
|
89
89
|
this.#db = null;
|
|
90
90
|
}
|
|
91
91
|
// -- TelemetryReader -----------------------------------------------------------
|
|
92
|
-
count(taskId,
|
|
92
|
+
count(taskId, runId) {
|
|
93
93
|
const db = this.#ensureDb();
|
|
94
94
|
if (db === null)
|
|
95
95
|
return 0;
|
|
96
96
|
if (taskId === undefined) {
|
|
97
97
|
return db.prepare("SELECT COUNT(*) AS n FROM telemetry").get().n;
|
|
98
98
|
}
|
|
99
|
-
if (
|
|
99
|
+
if (runId === undefined) {
|
|
100
100
|
return db.prepare("SELECT COUNT(*) AS n FROM telemetry WHERE task_id = ?").get(taskId).n;
|
|
101
101
|
}
|
|
102
|
-
return db.prepare("SELECT COUNT(*) AS n FROM telemetry WHERE task_id = ? AND turn_id = ?").get(taskId,
|
|
102
|
+
return db.prepare("SELECT COUNT(*) AS n FROM telemetry WHERE task_id = ? AND turn_id = ?").get(taskId, runId).n;
|
|
103
103
|
}
|
|
104
|
-
list(taskId,
|
|
104
|
+
list(taskId, runId, page = { limit: 100, offset: 0 }) {
|
|
105
105
|
const db = this.#ensureDb();
|
|
106
106
|
if (db === null)
|
|
107
107
|
return { items: [], nextOffset: null };
|
|
108
108
|
const limit = Math.min(Math.max(1, Math.trunc(page.limit)), MAX_PAGE_LIMIT);
|
|
109
109
|
const offset = Math.max(0, Math.trunc(page.offset));
|
|
110
|
-
const rows =
|
|
111
|
-
? db.prepare("SELECT task_id, role_name, turn_id,
|
|
112
|
-
: db.prepare("SELECT task_id, role_name, turn_id,
|
|
110
|
+
const rows = runId === undefined
|
|
111
|
+
? db.prepare("SELECT task_id, role_name, turn_id, progress_id, sequence, payload, received_at FROM telemetry WHERE task_id = ? ORDER BY received_at, progress_id LIMIT ? OFFSET ?").all(taskId, limit, offset)
|
|
112
|
+
: db.prepare("SELECT task_id, role_name, turn_id, progress_id, sequence, payload, received_at FROM telemetry WHERE task_id = ? AND turn_id = ? ORDER BY received_at, progress_id LIMIT ? OFFSET ?").all(taskId, runId, limit, offset);
|
|
113
113
|
const items = rows.map(rowToEntry);
|
|
114
|
-
const total = this.count(taskId,
|
|
114
|
+
const total = this.count(taskId, runId);
|
|
115
115
|
const nextOffset = offset + items.length < total ? offset + items.length : null;
|
|
116
116
|
return { items, nextOffset };
|
|
117
117
|
}
|
|
118
|
-
aggregate(taskId,
|
|
118
|
+
aggregate(taskId, runId) {
|
|
119
119
|
const db = this.#ensureDb();
|
|
120
120
|
if (db === null)
|
|
121
121
|
return null;
|
|
122
|
-
const rows = db.prepare("SELECT task_id, role_name, turn_id,
|
|
122
|
+
const rows = db.prepare("SELECT task_id, role_name, turn_id, first_at, last_at, count, max_sequence, error_count FROM telemetry_aggregate WHERE task_id = ? AND turn_id = ?").all(taskId, runId);
|
|
123
123
|
if (rows.length === 0)
|
|
124
124
|
return null;
|
|
125
125
|
return mergeAggregates(rows);
|
|
126
126
|
}
|
|
127
|
-
|
|
127
|
+
aggregateRoleRun(taskId, roleName, runId) {
|
|
128
128
|
const db = this.#ensureDb();
|
|
129
129
|
if (db === null)
|
|
130
130
|
return null;
|
|
131
|
-
const row = db.prepare("SELECT task_id, role_name, turn_id,
|
|
131
|
+
const row = db.prepare("SELECT task_id, role_name, turn_id, first_at, last_at, count, max_sequence, error_count FROM telemetry_aggregate WHERE task_id = ? AND role_name = ? AND turn_id = ?").get(taskId, roleName, runId);
|
|
132
132
|
if (row === undefined)
|
|
133
133
|
return null;
|
|
134
134
|
return {
|
|
135
135
|
taskId: row.task_id,
|
|
136
136
|
roleName: row.role_name,
|
|
137
|
-
|
|
138
|
-
generation: row.generation,
|
|
137
|
+
runId: row.turn_id,
|
|
139
138
|
firstAt: row.first_at,
|
|
140
139
|
lastAt: row.last_at,
|
|
141
140
|
count: row.count,
|
|
@@ -143,16 +142,15 @@ export class SqliteTelemetryStore {
|
|
|
143
142
|
errorCount: row.error_count
|
|
144
143
|
};
|
|
145
144
|
}
|
|
146
|
-
|
|
145
|
+
listRunAggregates(taskId) {
|
|
147
146
|
const db = this.#ensureDb();
|
|
148
147
|
if (db === null)
|
|
149
148
|
return [];
|
|
150
|
-
const rows = db.prepare("SELECT task_id, role_name, turn_id,
|
|
149
|
+
const rows = db.prepare("SELECT task_id, role_name, turn_id, first_at, last_at, count, max_sequence, error_count FROM telemetry_aggregate WHERE task_id = ? ORDER BY turn_id").all(taskId);
|
|
151
150
|
return rows.map((row) => ({
|
|
152
151
|
taskId: row.task_id,
|
|
153
152
|
roleName: row.role_name,
|
|
154
|
-
|
|
155
|
-
generation: row.generation,
|
|
153
|
+
runId: row.turn_id,
|
|
156
154
|
firstAt: row.first_at,
|
|
157
155
|
lastAt: row.last_at,
|
|
158
156
|
count: row.count,
|
|
@@ -164,52 +162,51 @@ export class SqliteTelemetryStore {
|
|
|
164
162
|
return this.#applied;
|
|
165
163
|
}
|
|
166
164
|
// -- retention -----------------------------------------------------------------
|
|
167
|
-
|
|
165
|
+
pruneRun(taskId, roleName, runId, keep = this.#terminalKeep) {
|
|
168
166
|
const db = this.#ensureDb();
|
|
169
167
|
if (db === null)
|
|
170
168
|
return 0;
|
|
171
169
|
const result = db.prepare(`DELETE FROM telemetry
|
|
172
|
-
WHERE task_id = ? AND role_name = ? AND turn_id = ?
|
|
173
|
-
AND (task_id, role_name, turn_id,
|
|
174
|
-
SELECT task_id, role_name, turn_id,
|
|
175
|
-
WHERE task_id = ? AND role_name = ? AND turn_id = ?
|
|
176
|
-
ORDER BY COALESCE(sequence, -1) DESC,
|
|
170
|
+
WHERE task_id = ? AND role_name = ? AND turn_id = ?
|
|
171
|
+
AND (task_id, role_name, turn_id, progress_id) NOT IN (
|
|
172
|
+
SELECT task_id, role_name, turn_id, progress_id FROM telemetry
|
|
173
|
+
WHERE task_id = ? AND role_name = ? AND turn_id = ?
|
|
174
|
+
ORDER BY received_at DESC, COALESCE(sequence, -1) DESC, progress_id ASC
|
|
177
175
|
LIMIT ?
|
|
178
|
-
)`).run(taskId, roleName,
|
|
176
|
+
)`).run(taskId, roleName, runId, taskId, roleName, runId, keep);
|
|
179
177
|
return result.changes;
|
|
180
178
|
}
|
|
181
|
-
|
|
179
|
+
capRun(taskId, runId, cap = this.#turnCap) {
|
|
182
180
|
const db = this.#ensureDb();
|
|
183
181
|
if (db === null)
|
|
184
182
|
return 0;
|
|
185
183
|
const result = db.prepare(`DELETE FROM telemetry
|
|
186
184
|
WHERE task_id = ? AND turn_id = ?
|
|
187
|
-
AND (task_id, role_name, turn_id,
|
|
188
|
-
SELECT task_id, role_name, turn_id,
|
|
185
|
+
AND (task_id, role_name, turn_id, progress_id) NOT IN (
|
|
186
|
+
SELECT task_id, role_name, turn_id, progress_id FROM telemetry
|
|
189
187
|
WHERE task_id = ? AND turn_id = ?
|
|
190
|
-
ORDER BY COALESCE(sequence, -1) DESC,
|
|
188
|
+
ORDER BY received_at DESC, COALESCE(sequence, -1) DESC, progress_id ASC
|
|
191
189
|
LIMIT ?
|
|
192
|
-
)`).run(taskId,
|
|
190
|
+
)`).run(taskId, runId, taskId, runId, cap);
|
|
193
191
|
return result.changes;
|
|
194
192
|
}
|
|
195
|
-
|
|
193
|
+
importRun(entries, aggregate) {
|
|
196
194
|
const db = this.#ensureDb();
|
|
197
195
|
if (db === null) {
|
|
198
196
|
this.#dropped += entries.length;
|
|
199
197
|
return;
|
|
200
198
|
}
|
|
201
|
-
const upsert = db.prepare(`INSERT INTO telemetry (task_id, role_name, turn_id,
|
|
202
|
-
VALUES (?, ?, ?, ?, ?, ?,
|
|
203
|
-
ON CONFLICT(task_id, role_name, turn_id,
|
|
199
|
+
const upsert = db.prepare(`INSERT INTO telemetry (task_id, role_name, turn_id, progress_id, sequence, payload, received_at)
|
|
200
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
201
|
+
ON CONFLICT(task_id, role_name, turn_id, progress_id) DO UPDATE SET
|
|
204
202
|
sequence = excluded.sequence,
|
|
205
203
|
payload = excluded.payload,
|
|
206
204
|
received_at = excluded.received_at
|
|
207
|
-
WHERE
|
|
208
|
-
OR (excluded.sequence IS telemetry.sequence AND excluded.received_at >= telemetry.received_at)`);
|
|
205
|
+
WHERE excluded.received_at >= telemetry.received_at`);
|
|
209
206
|
const upsertAggregate = db.prepare(`INSERT INTO telemetry_aggregate
|
|
210
|
-
(task_id, role_name, turn_id,
|
|
211
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?,
|
|
212
|
-
ON CONFLICT(task_id, role_name, turn_id
|
|
207
|
+
(task_id, role_name, turn_id, first_at, last_at, count, max_sequence, error_count, updated_at)
|
|
208
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
209
|
+
ON CONFLICT(task_id, role_name, turn_id) DO UPDATE SET
|
|
213
210
|
first_at = excluded.first_at,
|
|
214
211
|
last_at = excluded.last_at,
|
|
215
212
|
count = excluded.count,
|
|
@@ -218,9 +215,9 @@ export class SqliteTelemetryStore {
|
|
|
218
215
|
updated_at = excluded.updated_at`);
|
|
219
216
|
db.transaction(() => {
|
|
220
217
|
for (const entry of entries) {
|
|
221
|
-
upsert.run(entry.taskId, entry.roleName, entry.
|
|
218
|
+
upsert.run(entry.taskId, entry.roleName, entry.runId, entry.progressId, entry.sequence ?? null, JSON.stringify(entry.payload), entry.receivedAt);
|
|
222
219
|
}
|
|
223
|
-
upsertAggregate.run(aggregate.taskId, aggregate.roleName, aggregate.
|
|
220
|
+
upsertAggregate.run(aggregate.taskId, aggregate.roleName, aggregate.runId, aggregate.firstAt, aggregate.lastAt, aggregate.count, aggregate.maxSequence, aggregate.errorCount, aggregate.lastAt);
|
|
224
221
|
})();
|
|
225
222
|
this.#applied += entries.length;
|
|
226
223
|
}
|
|
@@ -277,26 +274,25 @@ export class SqliteTelemetryStore {
|
|
|
277
274
|
}
|
|
278
275
|
const batch = new Map(this.#pending);
|
|
279
276
|
this.#pending.clear();
|
|
280
|
-
const upsert = db.prepare(`INSERT INTO telemetry (task_id, role_name, turn_id,
|
|
281
|
-
VALUES (?, ?, ?, ?, ?, ?,
|
|
282
|
-
ON CONFLICT(task_id, role_name, turn_id,
|
|
277
|
+
const upsert = db.prepare(`INSERT INTO telemetry (task_id, role_name, turn_id, progress_id, sequence, payload, received_at)
|
|
278
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
279
|
+
ON CONFLICT(task_id, role_name, turn_id, progress_id) DO UPDATE SET
|
|
283
280
|
sequence = excluded.sequence,
|
|
284
281
|
payload = excluded.payload,
|
|
285
282
|
received_at = excluded.received_at
|
|
286
|
-
WHERE
|
|
287
|
-
OR (excluded.sequence IS telemetry.sequence AND excluded.received_at >= telemetry.received_at)`);
|
|
283
|
+
WHERE excluded.received_at >= telemetry.received_at`);
|
|
288
284
|
try {
|
|
289
|
-
const
|
|
285
|
+
const touchedRuns = new Map();
|
|
290
286
|
db.transaction(() => {
|
|
291
287
|
for (const entry of batch.values()) {
|
|
292
|
-
upsert.run(entry.taskId, entry.roleName, entry.
|
|
293
|
-
|
|
288
|
+
upsert.run(entry.taskId, entry.roleName, entry.runId, entry.progressId, entry.sequence ?? null, JSON.stringify(entry.payload), entry.receivedAt);
|
|
289
|
+
touchedRuns.set(`${entry.taskId}\0${entry.runId}`, {
|
|
294
290
|
taskId: entry.taskId,
|
|
295
|
-
|
|
291
|
+
runId: entry.runId
|
|
296
292
|
});
|
|
297
293
|
}
|
|
298
|
-
for (const { taskId,
|
|
299
|
-
this.
|
|
294
|
+
for (const { taskId, runId } of touchedRuns.values()) {
|
|
295
|
+
this.capRun(taskId, runId);
|
|
300
296
|
}
|
|
301
297
|
})();
|
|
302
298
|
this.#applied += batch.size;
|
|
@@ -308,25 +304,17 @@ export class SqliteTelemetryStore {
|
|
|
308
304
|
}
|
|
309
305
|
}
|
|
310
306
|
function pendingKey(entry) {
|
|
311
|
-
return `${entry.taskId}\u0000${entry.roleName}\u0000${entry.
|
|
307
|
+
return `${entry.taskId}\u0000${entry.roleName}\u0000${entry.runId}\u0000${entry.progressId}`;
|
|
312
308
|
}
|
|
313
|
-
/**
|
|
309
|
+
/** Transport counters may restart; the later recorded observation wins. */
|
|
314
310
|
function isNewer(candidate, current) {
|
|
315
|
-
if (candidate.sequence !== undefined && current.sequence !== undefined) {
|
|
316
|
-
return candidate.sequence > current.sequence;
|
|
317
|
-
}
|
|
318
|
-
if (candidate.sequence !== undefined)
|
|
319
|
-
return true;
|
|
320
|
-
if (current.sequence !== undefined)
|
|
321
|
-
return false;
|
|
322
311
|
return Date.parse(candidate.receivedAt) >= Date.parse(current.receivedAt);
|
|
323
312
|
}
|
|
324
313
|
function rowToEntry(row) {
|
|
325
314
|
return {
|
|
326
315
|
taskId: row.task_id,
|
|
327
316
|
roleName: row.role_name,
|
|
328
|
-
|
|
329
|
-
generation: row.generation,
|
|
317
|
+
runId: row.turn_id,
|
|
330
318
|
progressId: row.progress_id,
|
|
331
319
|
...(row.sequence === null ? {} : { sequence: row.sequence }),
|
|
332
320
|
payload: JSON.parse(row.payload),
|
|
@@ -356,8 +344,7 @@ function mergeAggregates(rows) {
|
|
|
356
344
|
return {
|
|
357
345
|
taskId: rows[0].task_id,
|
|
358
346
|
roleName,
|
|
359
|
-
|
|
360
|
-
generation: "*",
|
|
347
|
+
runId: rows[0].turn_id,
|
|
361
348
|
firstAt,
|
|
362
349
|
lastAt,
|
|
363
350
|
count,
|
|
@@ -6,34 +6,34 @@
|
|
|
6
6
|
* never changes canonical runtime-state persistence. Retention defaults are
|
|
7
7
|
* the schema's own constants (§4.4); the environment only overrides them.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import { TELEMETRY_KEEP_PER_RUN, TELEMETRY_RUN_CAP } from "../storage/sqliteSchema.js";
|
|
10
10
|
export const DEFAULT_TELEMETRY_MODE = "off";
|
|
11
|
-
/** Terminal
|
|
12
|
-
export const DEFAULT_TERMINAL_KEEP =
|
|
13
|
-
/** Hard cap of progress rows per
|
|
14
|
-
export const
|
|
11
|
+
/** Terminal AgentRun progress rows retained after prune. */
|
|
12
|
+
export const DEFAULT_TERMINAL_KEEP = TELEMETRY_KEEP_PER_RUN;
|
|
13
|
+
/** Hard cap of progress rows per AgentRun while it is still active. */
|
|
14
|
+
export const DEFAULT_RUN_CAP = TELEMETRY_RUN_CAP;
|
|
15
15
|
/**
|
|
16
|
-
* Absolute ceiling for the configurable
|
|
16
|
+
* Absolute ceiling for the configurable AgentRun cap. Retention can be tuned but
|
|
17
17
|
* never disabled: every Home keeps a computable upper bound on telemetry
|
|
18
|
-
* rows (Tasks ×
|
|
18
|
+
* rows (Tasks × AgentRuns × cap).
|
|
19
19
|
*/
|
|
20
|
-
export const
|
|
20
|
+
export const MAX_RUN_CAP = 10_000_000;
|
|
21
21
|
/**
|
|
22
|
-
* Resolve the terminal-
|
|
22
|
+
* Resolve the terminal-AgentRun retention window from the durable config value
|
|
23
23
|
* (default 200). Must be a positive integer.
|
|
24
24
|
*/
|
|
25
25
|
export function resolveTerminalKeep(value) {
|
|
26
26
|
return resolvePositiveInteger(value, DEFAULT_TERMINAL_KEEP, "telemetryTerminalKeep");
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
|
-
* Resolve the active-
|
|
29
|
+
* Resolve the active-AgentRun hard cap from the durable config value (default
|
|
30
30
|
* 50,000). Must be a positive integer not exceeding {@link MAX_TURN_CAP};
|
|
31
31
|
* the cap cannot be disabled.
|
|
32
32
|
*/
|
|
33
|
-
export function
|
|
34
|
-
const cap = resolvePositiveInteger(value,
|
|
35
|
-
if (cap >
|
|
36
|
-
throw new TypeError(`telemetryTurnCap must not exceed ${
|
|
33
|
+
export function resolveRunCap(value) {
|
|
34
|
+
const cap = resolvePositiveInteger(value, DEFAULT_RUN_CAP, "telemetryRunCap");
|
|
35
|
+
if (cap > MAX_RUN_CAP) {
|
|
36
|
+
throw new TypeError(`telemetryTurnCap must not exceed ${MAX_RUN_CAP.toLocaleString("en-US")} (retention cannot be disabled).`);
|
|
37
37
|
}
|
|
38
38
|
return cap;
|
|
39
39
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveRunCap, resolveTerminalKeep } from "./telemetryConfig.js";
|
|
4
4
|
import { resolveTelemetryEnabled } from "../config/yuiConfig.js";
|
|
5
5
|
import { SqliteTelemetryStore } from "./sqliteTelemetryStore.js";
|
|
6
6
|
import { CURRENT_DATABASE_FILENAME as COMMITTED_DATABASE_FILENAME } from "../storage/currentTaskStore.js";
|
|
@@ -22,7 +22,7 @@ export function openSchedulerTelemetry(home, config) {
|
|
|
22
22
|
const store = new SqliteTelemetryStore(home, {
|
|
23
23
|
mode,
|
|
24
24
|
terminalKeep: resolveTerminalKeep(config.telemetryTerminalKeep),
|
|
25
|
-
|
|
25
|
+
runCap: resolveRunCap(config.telemetryRunCap)
|
|
26
26
|
});
|
|
27
27
|
return { mode, sink: store, reader: store, retention: store };
|
|
28
28
|
}
|
|
@@ -8,6 +8,7 @@ import { I18N_SCRIPT } from "./client/i18n.js";
|
|
|
8
8
|
import { MARKDOWN_SCRIPT } from "./client/markdown.js";
|
|
9
9
|
import { THEME_SCRIPT } from "./client/theme.js";
|
|
10
10
|
import { VIEW_SCRIPT } from "./client/view.js";
|
|
11
|
+
import { TASK_SURFACE_SCRIPT } from "./client/taskSurface.js";
|
|
11
12
|
import { DASHBOARD_HTML } from "./shell.js";
|
|
12
13
|
import { FONT_FACE_STYLES } from "./fonts.js";
|
|
13
14
|
import { FONT_WOFF2_BASE64 } from "./fontData.js";
|
|
@@ -33,6 +34,7 @@ export const WEB_ASSETS = Object.freeze({
|
|
|
33
34
|
"/assets/js/theme.js": { contentType: "text/javascript; charset=utf-8", body: THEME_SCRIPT },
|
|
34
35
|
"/assets/js/components.js": { contentType: "text/javascript; charset=utf-8", body: COMPONENTS_SCRIPT },
|
|
35
36
|
"/assets/js/view.js": { contentType: "text/javascript; charset=utf-8", body: VIEW_SCRIPT },
|
|
37
|
+
"/assets/js/task-surface.js": { contentType: "text/javascript; charset=utf-8", body: TASK_SURFACE_SCRIPT },
|
|
36
38
|
"/assets/app.js": { contentType: "text/javascript; charset=utf-8", body: APP_SCRIPT },
|
|
37
39
|
...fontAssets(),
|
|
38
40
|
"/assets/vendor/xterm.mjs": vendorAsset("@xterm/xterm/lib/xterm.mjs", "text/javascript; charset=utf-8"),
|
|
@@ -47,9 +47,10 @@ const state = {
|
|
|
47
47
|
detail: null,
|
|
48
48
|
detailKey: null
|
|
49
49
|
};
|
|
50
|
-
const VALID_FILTERS = ["all", "active", "draft", "completed", "
|
|
50
|
+
const VALID_FILTERS = ["all", "active", "draft", "completed", "cancelled", "archived"];
|
|
51
51
|
let terminalSession = null;
|
|
52
52
|
let terminalStateKey = "terminal.closed";
|
|
53
|
+
const submittedRequests = new Set();
|
|
53
54
|
|
|
54
55
|
const i18n = createI18n(elements.locale);
|
|
55
56
|
createThemeController(elements.theme);
|
|
@@ -125,7 +126,18 @@ function syncUrlFromState(options) {
|
|
|
125
126
|
function detailActions() {
|
|
126
127
|
return {
|
|
127
128
|
answerInput: answerInput,
|
|
128
|
-
openTerminal: openTerminal
|
|
129
|
+
openTerminal: openTerminal,
|
|
130
|
+
inspect: inspectRecord,
|
|
131
|
+
sendMessage: (taskId, body, requestId) => submitMutation(taskId + "/messages",
|
|
132
|
+
"/api/tasks/" + encodeURIComponent(taskId) + "/messages", { body, requestId }),
|
|
133
|
+
updateTask: (taskId, patch, requestId) => submitMutation(taskId + "/metadata",
|
|
134
|
+
"/api/tasks/" + encodeURIComponent(taskId) + "/metadata", { patch, requestId }),
|
|
135
|
+
panels: (taskId) => requestJson("/api/tasks/" + encodeURIComponent(taskId) + "/panels",
|
|
136
|
+
{ signal: AbortSignal.timeout(3000) }),
|
|
137
|
+
readPanel: (taskId, ref, input) => requestJson("/api/tasks/" + encodeURIComponent(taskId) + "/panels", {
|
|
138
|
+
method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ ref, input }),
|
|
139
|
+
signal: AbortSignal.timeout(3000)
|
|
140
|
+
})
|
|
129
141
|
};
|
|
130
142
|
}
|
|
131
143
|
|
|
@@ -171,9 +183,30 @@ function detailKeyOf(detail) {
|
|
|
171
183
|
return text.length + ":" + hash;
|
|
172
184
|
}
|
|
173
185
|
|
|
186
|
+
// The optional observation arrives after the core snapshot has already been
|
|
187
|
+
// rendered, so the render key has to cover the observed facts the detail
|
|
188
|
+
// actually draws or they would stay stale until the core snapshot changed.
|
|
189
|
+
// Only the rendered identity is included: observation timestamps advance on
|
|
190
|
+
// every poll and would re-render the detail continuously.
|
|
191
|
+
function runtimeSignatureOf(detail) {
|
|
192
|
+
const roles = (detail.runtime && detail.runtime.roles) || [];
|
|
193
|
+
return detail.runtimeStatus + "|" + roles.map(function (role) {
|
|
194
|
+
const session = role.runtimeSession;
|
|
195
|
+
return role.name + ":" + (session
|
|
196
|
+
? session.nativeSessionId + "/" + session.status
|
|
197
|
+
: "-");
|
|
198
|
+
}).join(",");
|
|
199
|
+
}
|
|
200
|
+
|
|
174
201
|
function renderCurrentDetail(force) {
|
|
175
202
|
if (state.detail) {
|
|
176
|
-
|
|
203
|
+
// Polling must not replace an unsent draft or an in-flight form, including
|
|
204
|
+
// after the user has moved focus. This is view state, never Task state.
|
|
205
|
+
if (elements.detail.dataset.taskId === state.detail.task.id
|
|
206
|
+
&& (elements.detail.querySelector('[data-unsent="true"]')
|
|
207
|
+
|| elements.detail.contains(document.activeElement))) return;
|
|
208
|
+
const key = i18n.getLocale() + "|" + state.detailKey
|
|
209
|
+
+ "|" + runtimeSignatureOf(state.detail);
|
|
177
210
|
if (!force && key === renderedDetailKey) return;
|
|
178
211
|
renderTaskDetail(
|
|
179
212
|
elements.detail,
|
|
@@ -275,31 +308,76 @@ async function requestJson(path, options) {
|
|
|
275
308
|
...options,
|
|
276
309
|
headers: {
|
|
277
310
|
accept: "application/json",
|
|
311
|
+
"x-yui-web-token": token,
|
|
278
312
|
...(options && options.headers ? options.headers : {})
|
|
279
313
|
}
|
|
280
314
|
});
|
|
281
315
|
if (!response.ok) {
|
|
282
316
|
let message = "HTTP " + response.status;
|
|
317
|
+
let disposition = "unknown";
|
|
283
318
|
try {
|
|
284
319
|
const body = await response.json();
|
|
285
320
|
if (body && body.error) message = body.error;
|
|
321
|
+
if (body && body.disposition === "not-submitted") disposition = "not-submitted";
|
|
286
322
|
} catch {}
|
|
287
|
-
throw new Error(message);
|
|
323
|
+
throw Object.assign(new Error(message), { disposition });
|
|
288
324
|
}
|
|
289
325
|
return response.json();
|
|
290
326
|
}
|
|
291
327
|
|
|
328
|
+
async function submitMutation(key, path, body) {
|
|
329
|
+
if (submittedRequests.has(key)) throw new Error("An earlier submission is unresolved; read current facts.");
|
|
330
|
+
submittedRequests.add(key);
|
|
331
|
+
try {
|
|
332
|
+
const receipt = await requestJson(path, {
|
|
333
|
+
method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body)
|
|
334
|
+
});
|
|
335
|
+
submittedRequests.delete(key);
|
|
336
|
+
return receipt;
|
|
337
|
+
} catch (error) {
|
|
338
|
+
if (error.disposition === "not-submitted") submittedRequests.delete(key);
|
|
339
|
+
throw error;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
292
343
|
async function loadTaskDetail(taskId, showLoading) {
|
|
293
344
|
if (showLoading) {
|
|
294
345
|
renderLoading(elements.detail, i18n.t, "loading.detail");
|
|
295
346
|
elements.mainCol.scrollTop = 0;
|
|
296
347
|
}
|
|
297
348
|
const savedScrollTop = showLoading ? 0 : elements.mainCol.scrollTop;
|
|
298
|
-
const
|
|
349
|
+
const base = "/api/tasks/" + encodeURIComponent(taskId);
|
|
350
|
+
// Rendering consumes the current snapshot, not event pages. Reconnect uses
|
|
351
|
+
// this same read; the independent delta API retains its fixed-bound contract.
|
|
352
|
+
const core = await requestJson(base + "/context");
|
|
353
|
+
const taskEntry = core.records.find(function (entry) { return entry.ref.store === "task"; });
|
|
354
|
+
if (!taskEntry) throw new Error("Task reference unavailable.");
|
|
355
|
+
const task = taskEntry.omitted ? (await inspectRecord(taskId, taskEntry.ref)).value : taskEntry.value;
|
|
356
|
+
const previous = state.detail && state.detail.task.id === taskId ? state.detail : null;
|
|
357
|
+
const detail = {
|
|
358
|
+
task, core,
|
|
359
|
+
runtime: previous && previous.runtime,
|
|
360
|
+
runtimeStatus: previous ? previous.runtimeStatus : "waiting",
|
|
361
|
+
runtimeObservedAt: previous ? previous.runtimeObservedAt : new Date().toISOString()
|
|
362
|
+
};
|
|
299
363
|
if (state.selected !== taskId) return;
|
|
300
364
|
state.detail = detail;
|
|
301
|
-
state.detailKey = detailKeyOf(
|
|
302
|
-
renderCurrentDetail(
|
|
365
|
+
state.detailKey = detailKeyOf(core);
|
|
366
|
+
renderCurrentDetail();
|
|
367
|
+
// Optional observation does not participate in core readiness or cursor.
|
|
368
|
+
void requestJson(base, { signal: AbortSignal.timeout(1000) }).then(function (runtime) {
|
|
369
|
+
if (state.detail !== detail) return;
|
|
370
|
+
detail.runtime = runtime;
|
|
371
|
+
detail.runtimeStatus = "available";
|
|
372
|
+
detail.runtimeObservedAt = new Date().toISOString();
|
|
373
|
+
updateRuntimePanel(detail);
|
|
374
|
+
}).catch(function () {
|
|
375
|
+
if (state.detail !== detail) return;
|
|
376
|
+
detail.runtime = null;
|
|
377
|
+
detail.runtimeStatus = "unavailable";
|
|
378
|
+
detail.runtimeObservedAt = new Date().toISOString();
|
|
379
|
+
updateRuntimePanel(detail);
|
|
380
|
+
});
|
|
303
381
|
// Reveal the tab bar before measuring/scroll so anchors land correctly.
|
|
304
382
|
setDetailActive(true);
|
|
305
383
|
updateStickyOffsets();
|
|
@@ -314,6 +392,25 @@ async function loadTaskDetail(taskId, showLoading) {
|
|
|
314
392
|
syncTabHighlight();
|
|
315
393
|
}
|
|
316
394
|
|
|
395
|
+
function updateRuntimePanel(detail) {
|
|
396
|
+
const status = elements.detail.querySelector("[data-runtime-status]");
|
|
397
|
+
const value = elements.detail.querySelector("[data-runtime-value]");
|
|
398
|
+
if (status) status.textContent = detail.runtimeStatus + " · " + detail.runtimeObservedAt;
|
|
399
|
+
if (value) value.textContent = detail.runtime
|
|
400
|
+
? JSON.stringify({ roles: detail.runtime.roles, runtimeHealth: detail.runtime.runtimeHealth }, null, 2)
|
|
401
|
+
: "";
|
|
402
|
+
// Patching the panel in place is not enough: the observation also feeds facts
|
|
403
|
+
// the detail rendered before it arrived. Re-render when the observed identity
|
|
404
|
+
// has actually changed — renderCurrentDetail still compares the render key,
|
|
405
|
+
// so an unchanged observation costs nothing and an unsent form is preserved.
|
|
406
|
+
if (state.detail === detail) renderCurrentDetail();
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async function inspectRecord(taskId, ref) {
|
|
410
|
+
const query = new URLSearchParams({ store: ref.store, ref: ref.refId, digest: ref.digest });
|
|
411
|
+
return requestJson("/api/tasks/" + encodeURIComponent(taskId) + "/inspect?" + query);
|
|
412
|
+
}
|
|
413
|
+
|
|
317
414
|
async function selectTask(taskId) {
|
|
318
415
|
// Switching tasks drops the previous section; staying on the same task
|
|
319
416
|
// (URL-driven loads, popstate) keeps it.
|
|
@@ -345,27 +442,27 @@ async function selectTask(taskId) {
|
|
|
345
442
|
async function answerInput(input, answer) {
|
|
346
443
|
if (!state.detail) return;
|
|
347
444
|
const taskId = state.detail.task.id;
|
|
445
|
+
const key = taskId + "/input/" + input.id;
|
|
348
446
|
try {
|
|
349
|
-
await
|
|
447
|
+
await submitMutation(key,
|
|
350
448
|
"/api/tasks/" + encodeURIComponent(taskId)
|
|
351
449
|
+ "/inputs/" + encodeURIComponent(input.id) + "/answer",
|
|
352
|
-
|
|
353
|
-
method: "POST",
|
|
354
|
-
headers: {
|
|
355
|
-
"content-type": "application/json",
|
|
356
|
-
"x-yui-web-token": token
|
|
357
|
-
},
|
|
358
|
-
body: JSON.stringify(answer)
|
|
359
|
-
}
|
|
450
|
+
answer
|
|
360
451
|
);
|
|
361
452
|
showToast(i18n.t("input.answered"));
|
|
453
|
+
submittedRequests.delete(key);
|
|
362
454
|
await refreshDashboard({ quiet: true });
|
|
363
|
-
} catch {
|
|
364
|
-
showToast(i18n.
|
|
455
|
+
} catch (error) {
|
|
456
|
+
showToast(error.disposition === "not-submitted" ? error.message : i18n.getLocale().startsWith("zh")
|
|
457
|
+
? "回答结果未知;请刷新检查原问题,不要盲目重发。"
|
|
458
|
+
: "Answer outcome unknown; refresh the original question before resubmitting.");
|
|
365
459
|
}
|
|
366
460
|
}
|
|
367
461
|
|
|
462
|
+
let refreshing = false;
|
|
368
463
|
async function refreshDashboard(options) {
|
|
464
|
+
if (refreshing) return;
|
|
465
|
+
refreshing = true;
|
|
369
466
|
const quiet = options && options.quiet;
|
|
370
467
|
if (!quiet) {
|
|
371
468
|
elements.refresh.disabled = true;
|
|
@@ -386,8 +483,10 @@ async function refreshDashboard(options) {
|
|
|
386
483
|
if (previousInputs !== null && dashboard.counts.openInputs > previousInputs) {
|
|
387
484
|
showToast(i18n.t("input.new"));
|
|
388
485
|
}
|
|
389
|
-
if (state.selected
|
|
390
|
-
try { await loadTaskDetail(state.selected, false); } catch {
|
|
486
|
+
if (state.selected) {
|
|
487
|
+
try { await loadTaskDetail(state.selected, false); } catch {
|
|
488
|
+
showToast(i18n.getLocale().startsWith("zh") ? "连接不可用;保留上次读取。" : "Disconnected; showing the last read.");
|
|
489
|
+
}
|
|
391
490
|
}
|
|
392
491
|
} catch {
|
|
393
492
|
if (!quiet) {
|
|
@@ -395,6 +494,7 @@ async function refreshDashboard(options) {
|
|
|
395
494
|
showToast(i18n.t("errors.dashboard"));
|
|
396
495
|
}
|
|
397
496
|
} finally {
|
|
497
|
+
refreshing = false;
|
|
398
498
|
if (!quiet) elements.refresh.disabled = false;
|
|
399
499
|
}
|
|
400
500
|
}
|