@oar-sh/oar 0.9.9 → 0.9.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +561 -1
- package/README.md +162 -909
- package/bin/bat/oar.bat +46 -46
- package/package.json +14 -14
- package/server/README.md +369 -13
- package/server/claude-worker/claude-remote-relay-tool.mjs +10 -0
- package/server/claude-worker/claude-session-process.mjs +121 -9
- package/server/claude-worker/claude-session-worker.mjs +9 -1
- package/server/claude-worker/claude-turn-publisher.mjs +3 -0
- package/server/claude-worker/sdk-message-normalizer.mjs +31 -4
- package/server/claude-worker/subagent-timeline-recorder.mjs +572 -0
- package/server/copilot-worker/copilot-sdk-session-process.mjs +79 -12
- package/server/copilot-worker/copilot-sdk-session-worker.mjs +6 -0
- package/server/cursor-worker/cursor-sdk-adapter.mjs +56 -2
- package/server/db-schema.mjs +95 -2
- package/server/grok-worker/acp-client.mjs +2 -1
- package/server/grok-worker/acp-host-services.mjs +4 -3
- package/server/migrations/0009-subagent-timeline.mjs +135 -0
- package/server/migrations/0010-subagent-timeline-pruning.mjs +89 -0
- package/server/migrations/0011-relay-event-subscriptions.mjs +125 -0
- package/server/migrations/0012-cross-relay-event-subscriptions.mjs +116 -0
- package/server/migrations/0013-previews.mjs +130 -0
- package/server/migrations/0014-conversation-groups.mjs +89 -0
- package/server/migrations/0015-conversation-group-position.mjs +76 -0
- package/server/migrations/0016-share-access-log.mjs +110 -0
- package/server/public/app/about-ui.js +73 -0
- package/server/public/app/about-view.mjs +234 -0
- package/server/public/app/action-confirmations.js +31 -14
- package/server/public/app/agent-exchange-fold-view.mjs +187 -0
- package/server/public/app/api-client.js +312 -16
- package/server/public/app/archive-confirm.mjs +28 -0
- package/server/public/app/ask-user-view.js +17 -12
- package/server/public/app/attachments-view.js +516 -6
- package/server/public/app/background-tasks-view.mjs +18 -26
- package/server/public/app/bootstrap.js +336 -59
- package/server/public/app/chat-actions-menu-state.mjs +126 -0
- package/server/public/app/claude-cloud-conversation-ui.js +10 -22
- package/server/public/app/composer-control-state.mjs +16 -0
- package/server/public/app/composer-keyboard.mjs +54 -0
- package/server/public/app/context-menu.mjs +90 -6
- package/server/public/app/conversation-group-drag.mjs +191 -0
- package/server/public/app/conversation-group-order.mjs +265 -0
- package/server/public/app/conversation-group-state.mjs +100 -0
- package/server/public/app/conversation-list-drag.mjs +449 -0
- package/server/public/app/conversation-turn-state.mjs +81 -0
- package/server/public/app/conversation-view.js +436 -117
- package/server/public/app/cwd-picker.js +1 -1
- package/server/public/app/explorer-edit-state.mjs +68 -0
- package/server/public/app/hidden-messages-view.mjs +312 -0
- package/server/public/app/host-suspend-ui.js +13 -4
- package/server/public/app/journal-view.js +930 -45
- package/server/public/app/no-conversation-state.mjs +58 -0
- package/server/public/app/preview-cards.mjs +117 -31
- package/server/public/app/preview-command.mjs +10 -1
- package/server/public/app/provider-readiness.mjs +120 -0
- package/server/public/app/relay-board-view.js +19 -3
- package/server/public/app/relay-event-subscriptions-ui.js +184 -0
- package/server/public/app/relay-event-view.mjs +234 -0
- package/server/public/app/relay-restart-error.mjs +16 -0
- package/server/public/app/settings-modal.js +93 -9
- package/server/public/app/settings-tabs.js +1 -1
- package/server/public/app/share-format.mjs +49 -0
- package/server/public/app/share-preview-marks.mjs +167 -0
- package/server/public/app/share-state.js +109 -0
- package/server/public/app/shared-conversations-settings.js +480 -0
- package/server/public/app/socket-handlers.js +137 -40
- package/server/public/app/store.js +115 -32
- package/server/public/app/subagent-row.mjs +138 -0
- package/server/public/app/subagent-timeline-view.mjs +186 -0
- package/server/public/app/subagent-viewer-watch.mjs +50 -0
- package/server/public/app/subagent-viewer.mjs +558 -0
- package/server/public/app/task-row-format.mjs +29 -0
- package/server/public/app/transcript-jump-bottom-view.mjs +464 -0
- package/server/public/app/transcript-jump-bottom.mjs +210 -0
- package/server/public/app/transcript-reload-freshness.mjs +25 -0
- package/server/public/app/transcript-viewport-anchor.mjs +140 -0
- package/server/public/app/update-ui.js +1 -1
- package/server/public/app/viewer-history.mjs +5 -3
- package/server/public/index.html +774 -12
- package/server/relay-tools.md +2 -1
- package/server/relay.mjs +2 -1
- package/server/repositories/conversation-group-repository.mjs +444 -0
- package/server/repositories/message-repository.mjs +30 -3
- package/server/repositories/question-repository.mjs +9 -3
- package/server/repositories/remote-relay-repository.mjs +6 -2
- package/server/repositories/session-repository.mjs +12 -37
- package/server/repositories/share-repository.mjs +322 -0
- package/server/repositories/share-visibility-statements.mjs +10 -0
- package/server/repositories/subagent-timeline-statements.mjs +170 -0
- package/server/routes/about-routes.mjs +15 -0
- package/server/routes/ask-user-routes.mjs +3 -0
- package/server/routes/cache-routes.mjs +4 -0
- package/server/routes/conversation-group-routes.mjs +171 -0
- package/server/routes/messages-routes.mjs +698 -114
- package/server/routes/preview-routes.mjs +9 -1
- package/server/routes/relay-event-routes.mjs +157 -0
- package/server/routes/remote-relay-routes.mjs +15 -0
- package/server/routes/sessions-routes.mjs +843 -193
- package/server/server-runtime.mjs +559 -160
- package/server/services/about-service.mjs +461 -0
- package/server/services/background-task-store.mjs +128 -0
- package/server/services/background-work-view.mjs +228 -0
- package/server/services/cache-rebuild-service.mjs +30 -0
- package/server/services/delete-archive-service.mjs +4 -1
- package/server/services/detached-worker-watch-service.mjs +150 -0
- package/server/services/explorer-entry-name.mjs +26 -0
- package/server/services/explorer-mutations.mjs +122 -0
- package/server/services/hidden-message-list.mjs +21 -0
- package/server/services/host-suspend-command.mjs +2 -1
- package/server/services/message-pin-list.mjs +1 -1
- package/server/services/preview-health-probe.mjs +8 -1
- package/server/services/preview-proxy-server.mjs +49 -27
- package/server/services/preview-registry-service.mjs +676 -26
- package/server/services/preview-static-handler.mjs +40 -7
- package/server/services/preview-store.mjs +274 -0
- package/server/services/push-dispatch-service.mjs +18 -0
- package/server/services/rate-limit-service.mjs +12 -1
- package/server/services/relay-auth.mjs +183 -0
- package/server/services/relay-cli-launcher-service.mjs +3 -1
- package/server/services/relay-event-link-service.mjs +289 -0
- package/server/services/relay-event-service.mjs +1952 -0
- package/server/services/relay-shutdown-gate.mjs +31 -0
- package/server/services/remote-relay-caller-context.mjs +52 -7
- package/server/services/remote-relay-dispatcher.mjs +551 -7
- package/server/services/remote-relay-registry-service.mjs +11 -1
- package/server/services/request-boundary.mjs +183 -0
- package/server/services/session-activity-tracker.mjs +695 -0
- package/server/services/session-history-refresh-service.mjs +7 -0
- package/server/services/session-worker-launch-service.mjs +190 -6
- package/server/services/session-worker-process-service.mjs +10 -6
- package/server/services/share-access-log-service.mjs +123 -0
- package/server/services/share-path-redaction.mjs +424 -0
- package/server/services/stale-attempt-log-limiter.mjs +96 -0
- package/server/services/stale-recovery-clock-guard.mjs +57 -0
- package/server/services/stale-subagent-run-sweep.mjs +109 -0
- package/server/services/status-event-service.mjs +7 -3
- package/server/services/subagent-record-retention.mjs +164 -0
- package/server/services/subagent-timeline-service.mjs +328 -0
- package/server/services/subagent-viewer-rooms.mjs +119 -0
- package/server/services/tunnel-worker-path-guard.mjs +24 -41
- package/server/services/upgrade-socket-guard.mjs +16 -0
- package/server/services/windows-autostart-service.mjs +6 -1
- package/server/services/windows-boot-autostart-service.mjs +207 -23
- package/shared/preview-tool-core.mjs +15 -5
- package/shared/relay-events.mjs +371 -0
- package/shared/remote-relay-contract.mjs +160 -6
- package/shared/share-path-privacy.mjs +30 -0
- package/shared/subagent-record-retention.mjs +31 -0
- package/shared/subagent-timeline.mjs +79 -0
- package/shared/windows-system-tools.mjs +106 -0
- package/shared/worker-runtime/background-task-digest.mjs +30 -0
- package/shared/worker-runtime/heartbeat.mjs +24 -0
- package/shared/worker-runtime/kept-posts.mjs +83 -0
- package/shared/worker-runtime/worker-websocket-link.mjs +47 -15
|
@@ -112,6 +112,7 @@ import { STEER_FOLDED_TEXT, STEER_STOPPED_TEXT, STEER_TURN_FAILED_TEXT } from '.
|
|
|
112
112
|
import { buildSteerSettleFailure } from '../../shared/steer-settle-failure.mjs';
|
|
113
113
|
import { shouldEmitStreamUpdate } from '../../shared/stream-emit-gating.mjs';
|
|
114
114
|
import { withSteerNote } from '../../shared/steer-note.mjs';
|
|
115
|
+
import { isRelayEventDelivery } from '../../shared/relay-events.mjs';
|
|
115
116
|
import { isTransientRelayError } from '../../shared/worker-runtime/relay-errors.mjs';
|
|
116
117
|
import {
|
|
117
118
|
createTurnLiveness,
|
|
@@ -460,6 +461,10 @@ export function createCopilotSdkSessionRunner({
|
|
|
460
461
|
// heartbeat snapshot, and a 10 s wait would often outlast the window in
|
|
461
462
|
// which the runtime still holds the message (live check 2026-09-25).
|
|
462
463
|
onCancellableChange = () => {},
|
|
464
|
+
// The relay turned this worker's newest task set away (`stale`): the
|
|
465
|
+
// worker asks for a heartbeat, on which the relay learns which process
|
|
466
|
+
// holds the session and asks for the set again.
|
|
467
|
+
onBackgroundTaskSetStale = () => {},
|
|
463
468
|
dbg = () => {},
|
|
464
469
|
} = {}) {
|
|
465
470
|
let client = null;
|
|
@@ -627,7 +632,15 @@ export function createCopilotSdkSessionRunner({
|
|
|
627
632
|
let taskRefreshQueued = 0;
|
|
628
633
|
let lastTaskRefreshAt = 0;
|
|
629
634
|
let taskPublishTimer = null;
|
|
635
|
+
// The last set the relay took, as JSON, and the one on its way to it.
|
|
630
636
|
let lastPublishedTasksSignature = '';
|
|
637
|
+
let inFlightTasksSignature = null;
|
|
638
|
+
let taskPublishSeq = 0;
|
|
639
|
+
// Every post of the set carries its place in this worker's order
|
|
640
|
+
// (`epoch` tells this process from one before it): posts can overtake
|
|
641
|
+
// each other on the way, and the relay applies the newer one only.
|
|
642
|
+
const taskSetEpoch = Date.now();
|
|
643
|
+
let taskSetStaleNotedAt = 0;
|
|
631
644
|
// Cap expiry is bounded: a task whose cancel the runtime refuses (or that
|
|
632
645
|
// keeps failing) this many times is abandoned — dropped from the live set so
|
|
633
646
|
// it stops pinning the runtime — rather than held forever.
|
|
@@ -1569,27 +1582,77 @@ export function createCopilotSdkSessionRunner({
|
|
|
1569
1582
|
* semantics, the same route the Claude worker publishes on). Set changes
|
|
1570
1583
|
* publish at once; enrichment-only changes (a tool call, tokens) ride a
|
|
1571
1584
|
* trailing throttle. Advisory: a failed post never disturbs events.
|
|
1585
|
+
*
|
|
1586
|
+
* A set equal to the last one the relay TOOK (or to the one on its way)
|
|
1587
|
+
* is not sent again: the signature is recorded when the post succeeds, so
|
|
1588
|
+
* a failed post is retried by the next publish instead of being
|
|
1589
|
+
* remembered as sent.
|
|
1590
|
+
* `resync`: the relay asked for the set again on a heartbeat (it
|
|
1591
|
+
* restarted, or a post was lost); that goes out whatever the signature
|
|
1592
|
+
* says, flagged. Returns the post.
|
|
1572
1593
|
*/
|
|
1573
|
-
function publishBackgroundTasks({ immediate = false } = {}) {
|
|
1594
|
+
function publishBackgroundTasks({ immediate = false, resync = false } = {}) {
|
|
1574
1595
|
const run = () => {
|
|
1575
1596
|
if (taskPublishTimer) {
|
|
1576
1597
|
clearTimeout(taskPublishTimer);
|
|
1577
1598
|
taskPublishTimer = null;
|
|
1578
1599
|
}
|
|
1579
|
-
const tasks =
|
|
1600
|
+
const tasks = currentTaskCards();
|
|
1580
1601
|
const signature = JSON.stringify(tasks);
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1602
|
+
const unchanged = inFlightTasksSignature === null
|
|
1603
|
+
? signature === lastPublishedTasksSignature
|
|
1604
|
+
: signature === inFlightTasksSignature;
|
|
1605
|
+
if (!resync && unchanged) return Promise.resolve();
|
|
1606
|
+
const seq = ++taskPublishSeq;
|
|
1607
|
+
inFlightTasksSignature = signature;
|
|
1608
|
+
return api('POST', '/api/background-tasks', {
|
|
1609
|
+
conversationId: sdkSessionId,
|
|
1610
|
+
tasks,
|
|
1611
|
+
order: { epoch: taskSetEpoch, seq },
|
|
1612
|
+
...(resync ? { resync: true } : {}),
|
|
1613
|
+
})
|
|
1614
|
+
.then((answer) => {
|
|
1615
|
+
// Only the newest post says what the relay holds now.
|
|
1616
|
+
if (seq !== taskPublishSeq) return;
|
|
1617
|
+
inFlightTasksSignature = null;
|
|
1618
|
+
if (answer?.stale === true) {
|
|
1619
|
+
// Turned away, not taken: nothing is recorded as sent, and a
|
|
1620
|
+
// heartbeat is asked for (at most one per ten seconds; the
|
|
1621
|
+
// periodic beat repeats the relay's request anyway).
|
|
1622
|
+
dbg('the relay dropped the newest background task set', answer.reason || '');
|
|
1623
|
+
if (Date.now() - taskSetStaleNotedAt < 10_000) return;
|
|
1624
|
+
taskSetStaleNotedAt = Date.now();
|
|
1625
|
+
try { onBackgroundTaskSetStale(); } catch {}
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
lastPublishedTasksSignature = signature;
|
|
1629
|
+
})
|
|
1630
|
+
.catch((error) => {
|
|
1631
|
+
// Not taken: the signature stays at what the relay last took, so
|
|
1632
|
+
// the next publish of this set is sent, not skipped.
|
|
1633
|
+
if (seq === taskPublishSeq) inFlightTasksSignature = null;
|
|
1634
|
+
dbg('background task publish failed', error?.message || String(error));
|
|
1635
|
+
});
|
|
1585
1636
|
};
|
|
1586
|
-
if (immediate || !(taskPublishThrottleMs > 0))
|
|
1587
|
-
|
|
1588
|
-
return;
|
|
1589
|
-
}
|
|
1590
|
-
if (taskPublishTimer) return;
|
|
1637
|
+
if (resync || immediate || !(taskPublishThrottleMs > 0)) return run();
|
|
1638
|
+
if (taskPublishTimer) return Promise.resolve();
|
|
1591
1639
|
taskPublishTimer = setTimeout(run, taskPublishThrottleMs);
|
|
1592
1640
|
taskPublishTimer.unref?.();
|
|
1641
|
+
return Promise.resolve();
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
function currentTaskCards() {
|
|
1645
|
+
return sessionRpc.tasks ? registryCards() : shellTrackerCards();
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
/** The ids of the live background tasks, for the heartbeat's digest. */
|
|
1649
|
+
function backgroundTaskIds() {
|
|
1650
|
+
return currentTaskCards().map((task) => task.taskId);
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
/** The relay asked for the task set again (heartbeat answer). */
|
|
1654
|
+
function resyncBackgroundTasks() {
|
|
1655
|
+
return publishBackgroundTasks({ resync: true });
|
|
1593
1656
|
}
|
|
1594
1657
|
|
|
1595
1658
|
// ------------------------------------------------------- background tasks --
|
|
@@ -4216,7 +4279,8 @@ export function createCopilotSdkSessionRunner({
|
|
|
4216
4279
|
// message and drop the request it was working on (burn-in 2026-09-25).
|
|
4217
4280
|
// Not in a continuation: there the running work is the runtime's own
|
|
4218
4281
|
// follow-up, not a request of the user's to keep going with.
|
|
4219
|
-
|
|
4282
|
+
// Nor in an event message the relay wrote: the note speaks as the user.
|
|
4283
|
+
const prompt = withRelayContext(context?.prefix, turn.kind === 'continuation' || isRelayEventDelivery(message) ? body : withSteerNote(body));
|
|
4220
4284
|
// Re-checked AFTER the awaits and before anything is sent or registered.
|
|
4221
4285
|
// Nothing has been sent yet, so handing the row back to the normal path is
|
|
4222
4286
|
// free.
|
|
@@ -4698,6 +4762,8 @@ export function createCopilotSdkSessionRunner({
|
|
|
4698
4762
|
steeringState,
|
|
4699
4763
|
getSettleFailed,
|
|
4700
4764
|
acknowledgeSettleFailed,
|
|
4765
|
+
backgroundTaskIds,
|
|
4766
|
+
resyncBackgroundTasks,
|
|
4701
4767
|
cancelPushedMessage,
|
|
4702
4768
|
// The task panel's per-card Stop (`rpc.tasks.cancel`); also what a
|
|
4703
4769
|
// subagent lane's Stop tries — the runtime tracks sync agents in the same
|
|
@@ -4724,6 +4790,7 @@ export function createCopilotSdkSessionRunner({
|
|
|
4724
4790
|
? new Promise((resolve) => { setTimeout(resolve, taskRefreshMs + 5); }).then(() => taskRefreshChain)
|
|
4725
4791
|
: taskRefreshChain),
|
|
4726
4792
|
// Test seams / observability.
|
|
4793
|
+
_publishBackgroundTasks: publishBackgroundTasks,
|
|
4727
4794
|
_getState: () => ({
|
|
4728
4795
|
hasClient: !!client,
|
|
4729
4796
|
hasSession: !!session,
|
|
@@ -121,6 +121,7 @@ async function main() {
|
|
|
121
121
|
// A message became (or stopped being) cancellable: report it now rather
|
|
122
122
|
// than on the next 10 s heartbeat (coalesced by the controller).
|
|
123
123
|
onCancellableChange: () => heartbeat?.requestPulse(),
|
|
124
|
+
onBackgroundTaskSetStale: () => heartbeat?.requestPulse(),
|
|
124
125
|
canHandBackHeldDelivery: () => linkBridge.canHandBackHeldDelivery(),
|
|
125
126
|
dbg,
|
|
126
127
|
});
|
|
@@ -144,6 +145,11 @@ async function main() {
|
|
|
144
145
|
getSteeringState: () => turnRunner.steeringState(),
|
|
145
146
|
getSettleFailed: () => turnRunner.getSettleFailed(),
|
|
146
147
|
onSettleFailedHandled: (ids) => turnRunner.acknowledgeSettleFailed(ids),
|
|
148
|
+
// The relay holds the task set in memory only: the digest of the live
|
|
149
|
+
// task ids rides every beat, and a relay that is out of step (it
|
|
150
|
+
// restarted, a post was lost) asks for the set again.
|
|
151
|
+
getBackgroundTaskIds: () => turnRunner.backgroundTaskIds(),
|
|
152
|
+
onResyncBackgroundTasks: () => turnRunner.resyncBackgroundTasks(),
|
|
147
153
|
});
|
|
148
154
|
|
|
149
155
|
const wsLink = createWorkerWebSocketLink({
|
|
@@ -209,12 +209,66 @@ export function startCursorRun({
|
|
|
209
209
|
};
|
|
210
210
|
// A tool call is reported on both surfaces (the delta hook and the message
|
|
211
211
|
// stream) under the same id; either one opens and closes it.
|
|
212
|
+
//
|
|
213
|
+
// Not every tool call is reported. The SDK translates only the tools it has
|
|
214
|
+
// a type for and drops the rest on both surfaces: among them the wait on a
|
|
215
|
+
// background shell (`awaitToolCall`), web fetches and input to a running
|
|
216
|
+
// shell. All a dropped call leaves is the step's `tool-requests-listed`
|
|
217
|
+
// count, sent once the model has finished requesting tools; from there the
|
|
218
|
+
// run waits for them without a single event, for as long as the command it
|
|
219
|
+
// waits on takes. So the calls listed beyond those reported count as one
|
|
220
|
+
// open tool, which closes when the model writes again: the SDK tells
|
|
221
|
+
// nothing about such a call ending, and the next step is the first sign.
|
|
222
|
+
// A step without such calls keeps the model window once its reported
|
|
223
|
+
// tools have ended. Steps are told from the delta hook alone: it carries
|
|
224
|
+
// the count, the reported calls and the model's output in step order.
|
|
225
|
+
const stepCalls = new Set();
|
|
226
|
+
let stepListed = false;
|
|
227
|
+
let unreportedCalls = 0;
|
|
228
|
+
const UNREPORTED_TOOLS = 'unreported-tools';
|
|
229
|
+
const noteCallStarted = (callId) => {
|
|
230
|
+
const key = String(callId || '').trim();
|
|
231
|
+
if (!key || stepCalls.has(key)) return;
|
|
232
|
+
stepCalls.add(key);
|
|
233
|
+
// Reported after the count: it was one of the calls counted as unreported.
|
|
234
|
+
if (stepListed && unreportedCalls > 0 && --unreportedCalls === 0) liveness.end('tool', UNREPORTED_TOOLS);
|
|
235
|
+
};
|
|
236
|
+
const noteToolsListed = (callCount) => {
|
|
237
|
+
stepListed = true;
|
|
238
|
+
unreportedCalls = Math.max(0, (Number(callCount) || 0) - stepCalls.size);
|
|
239
|
+
if (unreportedCalls > 0) liveness.begin('tool', UNREPORTED_TOOLS, 'unreported tool');
|
|
240
|
+
};
|
|
241
|
+
const noteModelWriting = () => {
|
|
242
|
+
if (!stepListed) return;
|
|
243
|
+
stepListed = false;
|
|
244
|
+
unreportedCalls = 0;
|
|
245
|
+
stepCalls.clear();
|
|
246
|
+
liveness.end('tool', UNREPORTED_TOOLS);
|
|
247
|
+
};
|
|
212
248
|
const observe = (event) => {
|
|
213
249
|
if (event.source === 'delta') {
|
|
214
250
|
const update = event.update || {};
|
|
215
251
|
liveness.traffic(update.type);
|
|
216
|
-
|
|
217
|
-
|
|
252
|
+
switch (update.type) {
|
|
253
|
+
case 'tool-call-started':
|
|
254
|
+
noteCallStarted(update.callId);
|
|
255
|
+
liveness.begin('tool', update.callId, update.toolCall?.type);
|
|
256
|
+
break;
|
|
257
|
+
case 'tool-call-completed':
|
|
258
|
+
liveness.end('tool', update.callId);
|
|
259
|
+
break;
|
|
260
|
+
case 'tool-requests-listed':
|
|
261
|
+
noteToolsListed(update.callCount);
|
|
262
|
+
break;
|
|
263
|
+
case 'text-delta':
|
|
264
|
+
case 'thinking-delta':
|
|
265
|
+
case 'thinking-completed':
|
|
266
|
+
case 'partial-tool-call':
|
|
267
|
+
noteModelWriting();
|
|
268
|
+
break;
|
|
269
|
+
default:
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
218
272
|
return;
|
|
219
273
|
}
|
|
220
274
|
const message = event.message || {};
|
package/server/db-schema.mjs
CHANGED
|
@@ -14,6 +14,14 @@ import { migrateRemoteRelays } from './migrations/0005-remote-relays.mjs';
|
|
|
14
14
|
import { migrateWorkspaceRootSettings } from './migrations/0006-workspace-root-settings.mjs';
|
|
15
15
|
import { migrateClaudeCloud } from './migrations/0007-claude-cloud.mjs';
|
|
16
16
|
import { migrateMessagePins } from './migrations/0008-message-pins.mjs';
|
|
17
|
+
import { migrateSubagentTimeline } from './migrations/0009-subagent-timeline.mjs';
|
|
18
|
+
import { migrateSubagentTimelinePruning } from './migrations/0010-subagent-timeline-pruning.mjs';
|
|
19
|
+
import { migrateRelayEventSubscriptions } from './migrations/0011-relay-event-subscriptions.mjs';
|
|
20
|
+
import { migrateCrossRelayEventSubscriptions } from './migrations/0012-cross-relay-event-subscriptions.mjs';
|
|
21
|
+
import { migratePreviews } from './migrations/0013-previews.mjs';
|
|
22
|
+
import { migrateConversationGroups } from './migrations/0014-conversation-groups.mjs';
|
|
23
|
+
import { migrateConversationGroupPosition } from './migrations/0015-conversation-group-position.mjs';
|
|
24
|
+
import { migrateShareAccessLog } from './migrations/0016-share-access-log.mjs';
|
|
17
25
|
import { migrateImageConversationSchema } from './repositories/image-conversation-repository.mjs';
|
|
18
26
|
|
|
19
27
|
// Mirrors DEFAULT_RELAY_MODE in server-runtime.mjs; used only by the one-time
|
|
@@ -422,8 +430,8 @@ export const SCHEMA_SQL = `
|
|
|
422
430
|
ON workflow_runs(conversation_id);
|
|
423
431
|
|
|
424
432
|
-- Previews published mid-turn, pinned to the transcript. Snapshot JSON, not a
|
|
425
|
-
-- registry reference: the
|
|
426
|
-
--
|
|
433
|
+
-- registry reference: the card must still render after the preview closes
|
|
434
|
+
-- (or after a restart dropped it as no longer valid). Written with the
|
|
427
435
|
-- queue id (the response id does not exist yet mid-turn) and linked to the
|
|
428
436
|
-- response at finalize, exactly like relay_activity.
|
|
429
437
|
CREATE TABLE IF NOT EXISTS preview_cards (
|
|
@@ -441,6 +449,24 @@ export const SCHEMA_SQL = `
|
|
|
441
449
|
CREATE INDEX IF NOT EXISTS idx_preview_cards_queue
|
|
442
450
|
ON preview_cards(queue_message_id);
|
|
443
451
|
|
|
452
|
+
-- Published previews, restored at boot so their public links survive a
|
|
453
|
+
-- restart. Existing databases get the table from migration 0013 (same
|
|
454
|
+
-- columns; a test keeps the two declarations equal). Times are epoch ms.
|
|
455
|
+
-- Its index comes from the migration only, which skips a previews table of
|
|
456
|
+
-- other columns instead of failing the boot over it.
|
|
457
|
+
CREATE TABLE IF NOT EXISTS previews (
|
|
458
|
+
token TEXT PRIMARY KEY,
|
|
459
|
+
mode TEXT NOT NULL,
|
|
460
|
+
conversation_id TEXT,
|
|
461
|
+
label TEXT NOT NULL,
|
|
462
|
+
target_host TEXT,
|
|
463
|
+
target_port INTEGER,
|
|
464
|
+
root_dir TEXT,
|
|
465
|
+
created_at INTEGER NOT NULL,
|
|
466
|
+
last_hit_at INTEGER,
|
|
467
|
+
host_boot_id TEXT
|
|
468
|
+
);
|
|
469
|
+
|
|
444
470
|
CREATE TABLE IF NOT EXISTS uploaded_files (
|
|
445
471
|
sha256 TEXT PRIMARY KEY,
|
|
446
472
|
original_name TEXT,
|
|
@@ -742,6 +768,73 @@ try {
|
|
|
742
768
|
} catch (error) {
|
|
743
769
|
console.warn(`[message-pins] migration 0008 failed; retrying on next boot: ${error?.message || error}`);
|
|
744
770
|
}
|
|
771
|
+
// Sub-agent viewer: the timeline table and the run header columns. Readers
|
|
772
|
+
// tolerate their absence (runs fall back to the activity, thought and stream
|
|
773
|
+
// rows), so a failure is logged and retried next boot like 0008.
|
|
774
|
+
try {
|
|
775
|
+
migrateSubagentTimeline(db);
|
|
776
|
+
} catch (error) {
|
|
777
|
+
console.warn(`[subagent-timeline] migration 0009 failed; retrying on next boot: ${error?.message || error}`);
|
|
778
|
+
}
|
|
779
|
+
// Sub-agent record retention: the pruned marker and the sweep's index. The
|
|
780
|
+
// sweep checks for the column and does nothing without it, so a failure is
|
|
781
|
+
// logged and retried next boot like 0009.
|
|
782
|
+
try {
|
|
783
|
+
migrateSubagentTimelinePruning(db);
|
|
784
|
+
} catch (error) {
|
|
785
|
+
console.warn(`[subagent-timeline] migration 0010 failed; retrying on next boot: ${error?.message || error}`);
|
|
786
|
+
}
|
|
787
|
+
// Relay event subscriptions: two new tables. The event service checks for
|
|
788
|
+
// them and stays off without them, so a failure is logged and retried next
|
|
789
|
+
// boot like 0010.
|
|
790
|
+
try {
|
|
791
|
+
migrateRelayEventSubscriptions(db);
|
|
792
|
+
} catch (error) {
|
|
793
|
+
console.warn(`[relay-events] migration 0011 failed; retrying on next boot: ${error?.message || error}`);
|
|
794
|
+
}
|
|
795
|
+
// Relay event subscriptions across paired relays: columns on 0011's table and
|
|
796
|
+
// the link table. Without them the event service keeps to this relay's own
|
|
797
|
+
// sessions, so a failure is logged and retried next boot like 0011.
|
|
798
|
+
try {
|
|
799
|
+
migrateCrossRelayEventSubscriptions(db);
|
|
800
|
+
} catch (error) {
|
|
801
|
+
console.warn(`[relay-events] migration 0012 failed; retrying on next boot: ${error?.message || error}`);
|
|
802
|
+
}
|
|
803
|
+
// Persisted previews: one new table. The preview store checks for it and keeps
|
|
804
|
+
// previews in memory only without it, so a failure is logged and retried next
|
|
805
|
+
// boot like 0012.
|
|
806
|
+
try {
|
|
807
|
+
migratePreviews(db);
|
|
808
|
+
} catch (error) {
|
|
809
|
+
console.warn(`[previews] migration 0013 failed; retrying on next boot: ${error?.message || error}`);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// Session groups: the group table and the conversations' group column. The
|
|
813
|
+
// group repository checks for them and sees no groups without them, so a
|
|
814
|
+
// failure is logged and retried next boot like 0012.
|
|
815
|
+
try {
|
|
816
|
+
migrateConversationGroups(db);
|
|
817
|
+
} catch (error) {
|
|
818
|
+
console.warn(`[session-groups] migration 0014 failed; retrying on next boot: ${error?.message || error}`);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// A hand order inside a session group: one column. The group repository
|
|
822
|
+
// checks for it and keeps the activity order without it, so a failure is
|
|
823
|
+
// logged and retried next boot like 0014.
|
|
824
|
+
try {
|
|
825
|
+
migrateConversationGroupPosition(db);
|
|
826
|
+
} catch (error) {
|
|
827
|
+
console.warn(`[session-groups] migration 0015 failed; retrying on next boot: ${error?.message || error}`);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// Share lineages and the share access log: one column, one table. The share
|
|
831
|
+
// repository checks for them; without them a renew starts a fresh lineage and
|
|
832
|
+
// no open is logged, so a failure is logged and retried next boot like 0015.
|
|
833
|
+
try {
|
|
834
|
+
migrateShareAccessLog(db);
|
|
835
|
+
} catch (error) {
|
|
836
|
+
console.warn(`[shares] migration 0016 failed; retrying on next boot: ${error?.message || error}`);
|
|
837
|
+
}
|
|
745
838
|
|
|
746
839
|
const runtimeSessionColumns = db.prepare(`PRAGMA table_info(runtime_sessions)`).all().map((c) => c.name);
|
|
747
840
|
if (runtimeSessionColumns.length) {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
createTurnStalledError,
|
|
13
13
|
resolveTurnStallWindows,
|
|
14
14
|
} from '../../shared/worker-runtime/turn-liveness.mjs';
|
|
15
|
+
import { windowsSystemToolPath } from '../../shared/windows-system-tools.mjs';
|
|
15
16
|
|
|
16
17
|
function whichCommand(command = 'grok') {
|
|
17
18
|
const value = String(command || 'grok').trim() || 'grok';
|
|
@@ -522,7 +523,7 @@ export class AcpClient extends EventEmitter {
|
|
|
522
523
|
this.proc.kill();
|
|
523
524
|
if (process.platform === 'win32' && this.proc.pid) {
|
|
524
525
|
try {
|
|
525
|
-
this.spawnImpl('taskkill', ['/pid', String(this.proc.pid), '/t', '/f'], {
|
|
526
|
+
this.spawnImpl(windowsSystemToolPath('taskkill'), ['/pid', String(this.proc.pid), '/t', '/f'], {
|
|
526
527
|
stdio: 'ignore',
|
|
527
528
|
windowsHide: true,
|
|
528
529
|
});
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { spawn, spawnSync } from 'node:child_process';
|
|
14
14
|
import fs from 'node:fs/promises';
|
|
15
|
+
import { windowsSystemToolPath } from '../../shared/windows-system-tools.mjs';
|
|
15
16
|
|
|
16
17
|
const DEFAULT_OUTPUT_BYTE_LIMIT = 1024 * 1024;
|
|
17
18
|
const MAX_OUTPUT_BYTE_LIMIT = 8 * 1024 * 1024;
|
|
@@ -55,9 +56,9 @@ export function createAcpHostServices({
|
|
|
55
56
|
timeout: 10_000,
|
|
56
57
|
stdio: 'ignore',
|
|
57
58
|
});
|
|
58
|
-
cachedWindowsShell = probe && probe.status === 0 ? 'pwsh' : 'powershell';
|
|
59
|
+
cachedWindowsShell = probe && probe.status === 0 ? 'pwsh' : windowsSystemToolPath('powershell');
|
|
59
60
|
} catch {
|
|
60
|
-
cachedWindowsShell = 'powershell';
|
|
61
|
+
cachedWindowsShell = windowsSystemToolPath('powershell');
|
|
61
62
|
}
|
|
62
63
|
return cachedWindowsShell;
|
|
63
64
|
}
|
|
@@ -178,7 +179,7 @@ export function createAcpHostServices({
|
|
|
178
179
|
if (platform === 'win32' && entry.proc.pid) {
|
|
179
180
|
// proc.kill() only reaches the shell; shells leave grandchildren behind.
|
|
180
181
|
try {
|
|
181
|
-
spawnImpl('taskkill', ['/pid', String(entry.proc.pid), '/t', '/f'], {
|
|
182
|
+
spawnImpl(windowsSystemToolPath('taskkill'), ['/pid', String(entry.proc.pid), '/t', '/f'], {
|
|
182
183
|
stdio: 'ignore',
|
|
183
184
|
windowsHide: true,
|
|
184
185
|
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Migration: sub-agent timeline.
|
|
5
|
+
*
|
|
6
|
+
* 1. `subagent_timeline` — one row per entry of a sub-agent's record (a
|
|
7
|
+
* thought, a tool call, a tool result, reply text, a child agent, a note),
|
|
8
|
+
* in the order the relay first saw it (`seq`, per run). A writer names each
|
|
9
|
+
* entry with its own `entry_key`; a second write under the same key updates
|
|
10
|
+
* the entry in place (live text, a thought that is still streaming) and
|
|
11
|
+
* keeps its place in the order.
|
|
12
|
+
* 2. Header columns on `subagent_runs`: what the task panel shows (model,
|
|
13
|
+
* type, tokens) and what the viewer needs to know about the run (task id,
|
|
14
|
+
* background, whether the record was cut, who writes the timeline). The
|
|
15
|
+
* panel's own copy lives in memory only and is gone when the task settles.
|
|
16
|
+
*
|
|
17
|
+
* Additive and idempotent. Safe to run on every boot.
|
|
18
|
+
*
|
|
19
|
+
* Usage: node server/migrations/0009-subagent-timeline.mjs [path/to/copilot.db]
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { fileURLToPath } from 'url';
|
|
23
|
+
import path from 'path';
|
|
24
|
+
import process from 'process';
|
|
25
|
+
import Database from 'better-sqlite3';
|
|
26
|
+
|
|
27
|
+
function tableExists(db, name) {
|
|
28
|
+
return !!db
|
|
29
|
+
.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?`)
|
|
30
|
+
.get(name);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function indexExists(db, name) {
|
|
34
|
+
return !!db
|
|
35
|
+
.prepare(`SELECT name FROM sqlite_master WHERE type = 'index' AND name = ?`)
|
|
36
|
+
.get(name);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function columnNames(db, table) {
|
|
40
|
+
return new Set(db.prepare(`PRAGMA table_info(${table})`).all().map((column) => String(column.name)));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const SUBAGENT_RUN_HEADER_COLUMNS = Object.freeze([
|
|
44
|
+
['model', 'TEXT'],
|
|
45
|
+
['model_inherited', 'INTEGER'],
|
|
46
|
+
['subagent_type', 'TEXT'],
|
|
47
|
+
['task_id', 'TEXT'],
|
|
48
|
+
['total_tokens', 'INTEGER'],
|
|
49
|
+
['background', 'INTEGER NOT NULL DEFAULT 0'],
|
|
50
|
+
['truncated', 'INTEGER NOT NULL DEFAULT 0'],
|
|
51
|
+
['timeline_owner', 'TEXT'],
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
export function migrateSubagentTimeline(db) {
|
|
55
|
+
if (!tableExists(db, 'subagent_runs')) return { applied: false, tablesAdded: 0, columnsAdded: 0, indexesAdded: 0 };
|
|
56
|
+
let tablesAdded = 0;
|
|
57
|
+
let columnsAdded = 0;
|
|
58
|
+
let indexesAdded = 0;
|
|
59
|
+
|
|
60
|
+
const runColumns = columnNames(db, 'subagent_runs');
|
|
61
|
+
for (const [name, type] of SUBAGENT_RUN_HEADER_COLUMNS) {
|
|
62
|
+
if (runColumns.has(name)) continue;
|
|
63
|
+
db.exec(`ALTER TABLE subagent_runs ADD COLUMN ${name} ${type}`);
|
|
64
|
+
columnsAdded += 1;
|
|
65
|
+
}
|
|
66
|
+
if (!indexExists(db, 'idx_subagent_runs_parent')) {
|
|
67
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_subagent_runs_parent ON subagent_runs(parent_subagent_id)`);
|
|
68
|
+
indexesAdded += 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!tableExists(db, 'subagent_timeline')) {
|
|
72
|
+
db.exec(`
|
|
73
|
+
CREATE TABLE IF NOT EXISTS subagent_timeline (
|
|
74
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
75
|
+
run_id TEXT NOT NULL,
|
|
76
|
+
conversation_id TEXT NOT NULL,
|
|
77
|
+
queue_message_id TEXT,
|
|
78
|
+
entry_key TEXT NOT NULL,
|
|
79
|
+
seq INTEGER NOT NULL,
|
|
80
|
+
kind TEXT NOT NULL,
|
|
81
|
+
text TEXT,
|
|
82
|
+
tool_name TEXT,
|
|
83
|
+
tool_use_id TEXT,
|
|
84
|
+
metadata_json TEXT,
|
|
85
|
+
done INTEGER NOT NULL DEFAULT 1,
|
|
86
|
+
created_at TEXT NOT NULL,
|
|
87
|
+
updated_at TEXT NOT NULL,
|
|
88
|
+
UNIQUE (run_id, entry_key),
|
|
89
|
+
UNIQUE (run_id, seq),
|
|
90
|
+
FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE
|
|
91
|
+
)
|
|
92
|
+
`);
|
|
93
|
+
tablesAdded += 1;
|
|
94
|
+
}
|
|
95
|
+
if (!indexExists(db, 'idx_subagent_timeline_conversation')) {
|
|
96
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_subagent_timeline_conversation ON subagent_timeline(conversation_id)`);
|
|
97
|
+
indexesAdded += 1;
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
applied: tablesAdded + columnsAdded + indexesAdded > 0,
|
|
101
|
+
tablesAdded,
|
|
102
|
+
columnsAdded,
|
|
103
|
+
indexesAdded,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function defaultDbPath() {
|
|
108
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
109
|
+
return path.resolve(here, '..', 'data', 'copilot.db');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function migrate(dbPath, options = {}) {
|
|
113
|
+
const db = new Database(dbPath, options);
|
|
114
|
+
try {
|
|
115
|
+
return migrateSubagentTimeline(db);
|
|
116
|
+
} finally {
|
|
117
|
+
db.close();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
122
|
+
if (isMain) {
|
|
123
|
+
const dbPath = process.argv[2] ? path.resolve(process.argv[2]) : defaultDbPath();
|
|
124
|
+
try {
|
|
125
|
+
const result = migrate(dbPath);
|
|
126
|
+
console.log(`Migration 0009 on ${dbPath}`);
|
|
127
|
+
console.log(result.applied
|
|
128
|
+
? ` sub-agent timeline: tables added ${result.tablesAdded}, columns added ${result.columnsAdded}, indexes added ${result.indexesAdded}`
|
|
129
|
+
: ' skipped: nothing to change');
|
|
130
|
+
process.exit(0);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
console.error(`Migration 0009 failed: ${err?.message || err}`);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Migration: pruning old sub-agent records.
|
|
5
|
+
*
|
|
6
|
+
* After the retention period the relay strips a finished run's tool inputs
|
|
7
|
+
* and tool results (services/subagent-record-retention.mjs). `pruned_at`
|
|
8
|
+
* marks an entry it already stripped, and a partial index over the tool
|
|
9
|
+
* entries not stripped yet, by age, lets the daily sweep go straight to its
|
|
10
|
+
* candidates instead of reading the whole table every day.
|
|
11
|
+
*
|
|
12
|
+
* Additive and idempotent. Safe to run on every boot.
|
|
13
|
+
*
|
|
14
|
+
* Usage: node server/migrations/0010-subagent-timeline-pruning.mjs [path/to/copilot.db]
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { fileURLToPath } from 'url';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import process from 'process';
|
|
20
|
+
import Database from 'better-sqlite3';
|
|
21
|
+
|
|
22
|
+
export const PRUNABLE_INDEX_NAME = 'idx_subagent_timeline_prunable';
|
|
23
|
+
|
|
24
|
+
function tableExists(db, name) {
|
|
25
|
+
return !!db
|
|
26
|
+
.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?`)
|
|
27
|
+
.get(name);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function indexExists(db, name) {
|
|
31
|
+
return !!db
|
|
32
|
+
.prepare(`SELECT name FROM sqlite_master WHERE type = 'index' AND name = ?`)
|
|
33
|
+
.get(name);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function columnNames(db, table) {
|
|
37
|
+
return new Set(db.prepare(`PRAGMA table_info(${table})`).all().map((column) => String(column.name)));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function migrateSubagentTimelinePruning(db) {
|
|
41
|
+
if (!tableExists(db, 'subagent_timeline')) return { applied: false, columnsAdded: 0, indexesAdded: 0 };
|
|
42
|
+
let columnsAdded = 0;
|
|
43
|
+
let indexesAdded = 0;
|
|
44
|
+
if (!columnNames(db, 'subagent_timeline').has('pruned_at')) {
|
|
45
|
+
db.exec(`ALTER TABLE subagent_timeline ADD COLUMN pruned_at TEXT`);
|
|
46
|
+
columnsAdded += 1;
|
|
47
|
+
}
|
|
48
|
+
if (!indexExists(db, PRUNABLE_INDEX_NAME)) {
|
|
49
|
+
// The sweep's WHERE repeats this condition word for word, so SQLite can
|
|
50
|
+
// use the partial index.
|
|
51
|
+
db.exec(`
|
|
52
|
+
CREATE INDEX IF NOT EXISTS ${PRUNABLE_INDEX_NAME}
|
|
53
|
+
ON subagent_timeline(updated_at)
|
|
54
|
+
WHERE pruned_at IS NULL AND kind IN ('tool_call', 'tool_result')
|
|
55
|
+
`);
|
|
56
|
+
indexesAdded += 1;
|
|
57
|
+
}
|
|
58
|
+
return { applied: columnsAdded + indexesAdded > 0, columnsAdded, indexesAdded };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function defaultDbPath() {
|
|
62
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
63
|
+
return path.resolve(here, '..', 'data', 'copilot.db');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function migrate(dbPath, options = {}) {
|
|
67
|
+
const db = new Database(dbPath, options);
|
|
68
|
+
try {
|
|
69
|
+
return migrateSubagentTimelinePruning(db);
|
|
70
|
+
} finally {
|
|
71
|
+
db.close();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
76
|
+
if (isMain) {
|
|
77
|
+
const dbPath = process.argv[2] ? path.resolve(process.argv[2]) : defaultDbPath();
|
|
78
|
+
try {
|
|
79
|
+
const result = migrate(dbPath);
|
|
80
|
+
console.log(`Migration 0010 on ${dbPath}`);
|
|
81
|
+
console.log(result.applied
|
|
82
|
+
? ` sub-agent timeline pruning: columns added ${result.columnsAdded}, indexes added ${result.indexesAdded}`
|
|
83
|
+
: ' skipped: nothing to change');
|
|
84
|
+
process.exit(0);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.error(`Migration 0010 failed: ${err?.message || err}`);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
}
|