@oar-sh/oar 0.9.18 → 0.9.20
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/.github/extensions/web-relay/polling/polling-loop.mjs +2 -26
- package/.github/extensions/web-relay/server-lifecycle/managed-server.mjs +13 -1
- package/.github/extensions/web-relay/skills/question-routing-hooks.mjs +2 -26
- package/CHANGELOG.md +359 -0
- package/README.md +10 -10
- package/bin/oar.js +391 -97
- package/package.json +4 -2
- package/server/claude-cloud-worker/claude-cloud-event-normalizer.mjs +60 -0
- package/server/claude-cloud-worker/claude-cloud-session-process.mjs +27 -0
- package/server/claude-worker/claude-remote-relay-tool.mjs +3 -2
- package/server/claude-worker/claude-session-process.mjs +3 -0
- package/server/claude-worker/claude-turn-publisher.mjs +163 -8
- package/server/claude-worker/claude-usage-totals.mjs +58 -0
- package/server/copilot-worker/copilot-plan-board.mjs +4 -7
- package/server/copilot-worker/copilot-sdk-event-normalizer.mjs +86 -3
- package/server/copilot-worker/copilot-sdk-session-process.mjs +57 -4
- package/server/cursor-worker/cursor-turn-runner.mjs +42 -6
- package/server/db-schema.mjs +18 -0
- package/server/deepseek-worker/deepseek-claude-backend.mjs +14 -0
- package/server/grok-worker/grok-turn-runner.mjs +44 -6
- package/server/migrations/0001-add-structured-answer.mjs +5 -7
- package/server/migrations/0002-recent-workspace-roots-path-key.mjs +5 -7
- package/server/migrations/0003-push-subscriptions.mjs +5 -7
- package/server/migrations/0004-steer-settle-markers.mjs +5 -7
- package/server/migrations/0005-remote-relays.mjs +5 -7
- package/server/migrations/0006-workspace-root-settings.mjs +5 -7
- package/server/migrations/0007-claude-cloud.mjs +5 -7
- package/server/migrations/0008-message-pins.mjs +5 -7
- package/server/migrations/0009-subagent-timeline.mjs +5 -7
- package/server/migrations/0010-subagent-timeline-pruning.mjs +5 -7
- package/server/migrations/0011-relay-event-subscriptions.mjs +5 -7
- package/server/migrations/0012-cross-relay-event-subscriptions.mjs +5 -7
- package/server/migrations/0013-previews.mjs +5 -7
- package/server/migrations/0014-conversation-groups.mjs +5 -7
- package/server/migrations/0015-conversation-group-position.mjs +5 -7
- package/server/migrations/0016-share-access-log.mjs +5 -7
- package/server/migrations/0017-conversation-history-indexes.mjs +5 -7
- package/server/migrations/0018-background-task-rows.mjs +5 -7
- package/server/migrations/0019-relay-event-subscription-options.mjs +5 -7
- package/server/migrations/0020-conversation-may-create-sessions.mjs +5 -7
- package/server/migrations/0021-conversation-group-agent-marks.mjs +5 -7
- package/server/migrations/0022-relay-schedules.mjs +5 -7
- package/server/migrations/0023-relay-asks.mjs +5 -7
- package/server/migrations/0024-deepseek-usage-totals.mjs +5 -7
- package/server/migrations/0025-deepseek-usage-requests-daily.mjs +5 -7
- package/server/migrations/0026-deepseek-usage-served-models.mjs +5 -7
- package/server/migrations/0027-deepseek-usage-checkpoint-runs.mjs +5 -7
- package/server/migrations/0028-deepseek-usage-checkpoint-request-runs.mjs +5 -7
- package/server/migrations/0029-deepseek-balance-rises.mjs +5 -7
- package/server/migrations/0030-deepseek-usage-actuals.mjs +5 -7
- package/server/migrations/0031-queue-provider.mjs +5 -7
- package/server/migrations/0032-runtime-session-billing-switch.mjs +5 -7
- package/server/migrations/0033-claude-api-usage-checkpoints.mjs +5 -7
- package/server/migrations/0034-claude-api-usage-totals.mjs +5 -7
- package/server/migrations/0035-conversation-plan-only.mjs +87 -0
- package/server/migrations/0036-token-usage.mjs +267 -0
- package/server/public/app/about-view.mjs +87 -3
- package/server/public/app/api-client.js +2 -1
- package/server/public/app/bootstrap.js +55 -2
- package/server/public/app/composer-control-state.mjs +12 -2
- package/server/public/app/context-usage-view.mjs +16 -0
- package/server/public/app/conversation-view.js +17 -2
- package/server/public/app/inbox-ui.js +32 -0
- package/server/public/app/inbox-view.mjs +141 -5
- package/server/public/app/plan-board-shared.mjs +49 -0
- package/server/public/app/plan-usage-view.mjs +1 -1
- package/server/public/app/relay-board-inline.mjs +4 -1
- package/server/public/app/relay-board-view.js +38 -10
- package/server/public/app/relay-event-view.mjs +4 -0
- package/server/public/app/socket-handlers.js +10 -2
- package/server/public/index.html +14 -0
- package/server/public/sw.js +8 -0
- package/server/relay-heap-guard.mjs +351 -0
- package/server/relay-self-restart.mjs +116 -9
- package/server/relay-tools.md +5 -4
- package/server/repositories/message-repository.mjs +31 -1
- package/server/repositories/remote-relay-repository.mjs +10 -0
- package/server/repositories/session-repository.mjs +6 -1
- package/server/routes/messages-routes.mjs +120 -4
- package/server/routes/relay-ask-routes.mjs +138 -8
- package/server/routes/relay-board-routes.mjs +55 -40
- package/server/routes/sessions-routes.mjs +141 -31
- package/server/server-runtime.mjs +142 -11
- package/server/services/about-service.mjs +59 -0
- package/server/services/cache-rebuild-service.mjs +7 -0
- package/server/services/conversation-preferences-service.mjs +31 -0
- package/server/services/database-file.mjs +413 -0
- package/server/services/db-backup-service.mjs +809 -0
- package/server/services/deepseek-usage-import.mjs +6 -9
- package/server/services/delete-archive-service.mjs +3 -0
- package/server/services/earlier-database-service.mjs +69 -126
- package/server/services/oar-cli-helpers.mjs +214 -12
- package/server/services/oar-cli-menu.mjs +1 -0
- package/server/services/oar-state-migration-service.mjs +33 -13
- package/server/services/package-data-rescue.mjs +4 -2
- package/server/services/plan-usage-claude-api.mjs +14 -15
- package/server/services/plan-usage-contract.mjs +6 -2
- package/server/services/plan-usage-deepseek.mjs +1 -1
- package/server/services/plan-usage-tokens.mjs +248 -0
- package/server/services/push-dispatch-service.mjs +134 -9
- package/server/services/relay-cli-launcher-service.mjs +3 -0
- package/server/services/relay-event-service.mjs +30 -0
- package/server/services/relay-question-context.mjs +11 -0
- package/server/services/relay-restart-picture.mjs +67 -22
- package/server/services/remote-inbox-watch-service.mjs +204 -0
- package/server/services/remote-relay-caller-context.mjs +80 -7
- package/server/services/remote-relay-dispatcher.mjs +68 -8
- package/server/services/restart-resume-service.mjs +5 -2
- package/server/services/session-worker-launch-service.mjs +12 -3
- package/server/services/session-worker-stop-service.mjs +27 -6
- package/server/services/token-usage-service.mjs +430 -0
- package/server/services/update-install-service.mjs +13 -0
- package/server/services/usage-limit-pause-service.mjs +8 -1
- package/server/services/worker-scope.mjs +77 -0
- package/shared/plan-board-actions.mjs +130 -0
- package/shared/plan-only.mjs +43 -0
- package/shared/relay-asks.mjs +3 -0
- package/shared/relay-events.mjs +38 -4
- package/shared/remote-relay-contract.mjs +98 -21
- package/shared/turn-token-usage.mjs +144 -0
|
@@ -523,6 +523,14 @@ export function createCopilotSdkSessionRunner({
|
|
|
523
523
|
// The in-flight ingest POST, exposed only as a test seam. Never awaited by
|
|
524
524
|
// the turn path — that is the entire point of it.
|
|
525
525
|
let usagePostChain = Promise.resolve();
|
|
526
|
+
// The token booking's post (POST /api/turn-token-usage), a chain and a test
|
|
527
|
+
// seam like `usagePostChain`. Its run is the row's attempt (a row
|
|
528
|
+
// delivered again is another run of the turn); a row without one (an older
|
|
529
|
+
// relay) is booked under this process's own id, so a turn requeued onto a
|
|
530
|
+
// restarted worker is a second run of it, not the same row.
|
|
531
|
+
let tokenPostChain = Promise.resolve();
|
|
532
|
+
const postedTurnTokens = new WeakSet();
|
|
533
|
+
const tokenRunId = randomUUID();
|
|
526
534
|
// The last catalog content this worker POSTed to `/api/models/snapshot`,
|
|
527
535
|
// as a serialized signature. Identity of CONTENT, not of session: an idle
|
|
528
536
|
// shutdown and resume onto the same catalog must not re-publish it.
|
|
@@ -1330,6 +1338,7 @@ export function createCopilotSdkSessionRunner({
|
|
|
1330
1338
|
conversationId: message.conversationId,
|
|
1331
1339
|
sdkSessionId,
|
|
1332
1340
|
messageId: message.id,
|
|
1341
|
+
attemptId: message.attemptId || null,
|
|
1333
1342
|
model: result?.model || appliedModel() || defaultModel || '',
|
|
1334
1343
|
usage,
|
|
1335
1344
|
contextUsage,
|
|
@@ -1352,6 +1361,7 @@ export function createCopilotSdkSessionRunner({
|
|
|
1352
1361
|
hasQuotaSnapshots: !!usage?.quotaSnapshots,
|
|
1353
1362
|
cfiOverage: usage?.quotaSnapshots?.cfi_overage ?? null,
|
|
1354
1363
|
}));
|
|
1364
|
+
return lastTurnUsage;
|
|
1355
1365
|
}
|
|
1356
1366
|
|
|
1357
1367
|
/**
|
|
@@ -1372,9 +1382,10 @@ export function createCopilotSdkSessionRunner({
|
|
|
1372
1382
|
* row still open — for as long as the socket stayed up. The result is unused
|
|
1373
1383
|
* and the failure is already swallowed, so there was nothing to wait for.
|
|
1374
1384
|
*
|
|
1375
|
-
* BYOK sessions do not post
|
|
1385
|
+
* BYOK sessions do not post here: they spend the user's own OpenAI key
|
|
1376
1386
|
* rather than Copilot quota, and their usage events report `cost: 0`, so
|
|
1377
|
-
* their numbers would only mislead on a card about the Copilot plan.
|
|
1387
|
+
* their numbers would only mislead on a card about the Copilot plan. Their
|
|
1388
|
+
* tokens still go to the token booking (postTurnTokens).
|
|
1378
1389
|
*/
|
|
1379
1390
|
function postTurnUsage() {
|
|
1380
1391
|
if (byokProvider || !lastTurnUsage || lastTurnUsage === postedTurnUsage) return;
|
|
@@ -1385,6 +1396,39 @@ export function createCopilotSdkSessionRunner({
|
|
|
1385
1396
|
.catch((error) => { dbg('usage ingest failed', error?.message || String(error)); });
|
|
1386
1397
|
}
|
|
1387
1398
|
|
|
1399
|
+
/**
|
|
1400
|
+
* Report a turn's own captured tokens per model to the token booking,
|
|
1401
|
+
* from that turn's capture: a continuation that captured its
|
|
1402
|
+
* usage meanwhile cannot take the turn's place. Fire-and-forget like the plan usage, but BYOK sessions post
|
|
1403
|
+
* too, under their own billing (`byok-<provider type>`), which the relay
|
|
1404
|
+
* keeps apart from the plan's. No cost: the plan bills in AI units, not
|
|
1405
|
+
* dollars, and an own key's calls report 0.
|
|
1406
|
+
*/
|
|
1407
|
+
function postTurnTokens(capture) {
|
|
1408
|
+
if (!capture || postedTurnTokens.has(capture)) return;
|
|
1409
|
+
postedTurnTokens.add(capture);
|
|
1410
|
+
const models = (capture.usage?.models || []).map((entry) => ({
|
|
1411
|
+
model: entry.model,
|
|
1412
|
+
inputTokens: entry.inputTokens,
|
|
1413
|
+
outputTokens: entry.outputTokens,
|
|
1414
|
+
cacheWriteTokens: entry.cacheWriteTokens,
|
|
1415
|
+
cacheReadTokens: entry.cacheReadTokens,
|
|
1416
|
+
reasoningTokens: entry.reasoningTokens,
|
|
1417
|
+
}));
|
|
1418
|
+
if (!models.length || !capture.messageId) return;
|
|
1419
|
+
const billing = byokProvider ? `byok-${String(byokProvider.type || 'openai').trim().toLowerCase()}` : '';
|
|
1420
|
+
tokenPostChain = api('POST', '/api/turn-token-usage', {
|
|
1421
|
+
conversationId: capture.conversationId,
|
|
1422
|
+
turnId: capture.messageId,
|
|
1423
|
+
runId: capture.attemptId || tokenRunId,
|
|
1424
|
+
provider: 'github',
|
|
1425
|
+
billing,
|
|
1426
|
+
...(sdkSessionId ? { nativeSessionId: sdkSessionId } : {}),
|
|
1427
|
+
at: capture.capturedAt,
|
|
1428
|
+
models,
|
|
1429
|
+
}).catch((error) => { dbg('turn token ingest failed', error?.message || String(error)); });
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1388
1432
|
/**
|
|
1389
1433
|
* Publish the session's model catalog to `/api/models/snapshot` (Phase 5A:
|
|
1390
1434
|
* with the extension retired, worker snapshots and the server-side discovery
|
|
@@ -3161,7 +3205,14 @@ export function createCopilotSdkSessionRunner({
|
|
|
3161
3205
|
model: '',
|
|
3162
3206
|
// The runner's cross-turn knowledge of background agents rides in, so a
|
|
3163
3207
|
// later turn's normalizer opens no lane for an agent spawned earlier.
|
|
3164
|
-
normalizer: createNormalizerImpl({
|
|
3208
|
+
normalizer: createNormalizerImpl({
|
|
3209
|
+
isBackgroundAgent: (agentId) => agentDetails.has(agentId),
|
|
3210
|
+
// The raw counts of every model call, for checking the token rule
|
|
3211
|
+
// against a real provider (counts only).
|
|
3212
|
+
...(String(env?.OAR_DEBUG_TOKEN_USAGE || '').trim() === '1'
|
|
3213
|
+
? { debugTokenUsage: (fields) => dbg('token usage', JSON.stringify(fields)) }
|
|
3214
|
+
: {}),
|
|
3215
|
+
}),
|
|
3165
3216
|
result: null,
|
|
3166
3217
|
failure: null,
|
|
3167
3218
|
settled: false,
|
|
@@ -3730,7 +3781,7 @@ export function createCopilotSdkSessionRunner({
|
|
|
3730
3781
|
const failure = turn.failure;
|
|
3731
3782
|
// Capture only. The POST fires from driveTurn's `finally`, after the rows
|
|
3732
3783
|
// have been published.
|
|
3733
|
-
captureTurnUsage(turn.primaryEntry.message, result);
|
|
3784
|
+
turn.usageCapture = captureTurnUsage(turn.primaryEntry.message, result) || null;
|
|
3734
3785
|
|
|
3735
3786
|
const pending = turn.entries.filter((entry) => !entry.settled && entry.turn === turn);
|
|
3736
3787
|
if (!pending.length) return;
|
|
@@ -4166,6 +4217,7 @@ export function createCopilotSdkSessionRunner({
|
|
|
4166
4217
|
syncDeliveryReadiness();
|
|
4167
4218
|
// The ingest is advisory, never awaited, and must never be able to
|
|
4168
4219
|
// delay a reply that is already written.
|
|
4220
|
+
postTurnTokens(turn.usageCapture);
|
|
4169
4221
|
postTurnUsage();
|
|
4170
4222
|
}
|
|
4171
4223
|
}
|
|
@@ -5024,6 +5076,7 @@ export function createCopilotSdkSessionRunner({
|
|
|
5024
5076
|
// never awaits this, which is what keeps a slow relay from holding a
|
|
5025
5077
|
// finished reply.
|
|
5026
5078
|
whenUsagePosted: () => usagePostChain,
|
|
5079
|
+
whenTokensPosted: () => tokenPostChain,
|
|
5027
5080
|
// The in-flight model-catalog snapshot POST — the same kind of seam.
|
|
5028
5081
|
whenModelSnapshotPosted: () => modelSnapshotChain,
|
|
5029
5082
|
// The in-flight queued-lane read — same kind of seam.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
1
2
|
import { buildCursorUserMessage } from './cursor-attachments.mjs';
|
|
2
3
|
import { buildCursorContextUsage } from './cursor-context-usage.mjs';
|
|
3
4
|
import { createSdkMessageNormalizer } from './sdk-message-normalizer.mjs';
|
|
@@ -23,6 +24,7 @@ import { renderMediaEmbedInstructionBlock } from '../../shared/media-embed-instr
|
|
|
23
24
|
import { EMPTY_TURN_COMPLETION_NOTE } from '../../shared/empty-turn-completion.mjs';
|
|
24
25
|
import { resolveFallbackContextLimitTokens } from '../../shared/context-window-fallbacks.mjs';
|
|
25
26
|
import { countPlanLikeLines } from '../../shared/plan-lines.mjs';
|
|
27
|
+
import { PLAN_BOARD_ACTIONS } from '../../shared/plan-board-actions.mjs';
|
|
26
28
|
import { explainLaunchFailure, missingWorkingDirectoryError } from '../../shared/worker-runtime/working-directory.mjs';
|
|
27
29
|
|
|
28
30
|
// Stale-auth recovery budget per turn: expiry is routine on long-lived
|
|
@@ -34,12 +36,6 @@ const MAX_STALE_AUTH_RETRIES = 2;
|
|
|
34
36
|
// exchanged auth goes stale around the 4h mark while the API key stays valid.
|
|
35
37
|
const STALE_HANDLE_IDLE_MS = 30 * 60_000;
|
|
36
38
|
|
|
37
|
-
const PLAN_BOARD_ACTIONS = [
|
|
38
|
-
{ id: 'autopilot', label: 'Implement in autopilot', mode: 'autopilot' },
|
|
39
|
-
{ id: 'interactive', label: 'Stop here and prompt myself', mode: 'agent' },
|
|
40
|
-
{ id: 'exit_only', label: 'Stop here', mode: 'agent' },
|
|
41
|
-
];
|
|
42
|
-
|
|
43
39
|
// The Cursor SDK's SendOptions carries only model/mode/callbacks — there is
|
|
44
40
|
// no per-turn instruction channel — so ask/autopilot steering rides on the
|
|
45
41
|
// user message text, mirroring the Copilot extension's prompt-context
|
|
@@ -447,6 +443,45 @@ export function createCursorTurnRunner({
|
|
|
447
443
|
});
|
|
448
444
|
}
|
|
449
445
|
|
|
446
|
+
// A process without the row's attempt id names its run once (POST
|
|
447
|
+
// /api/turn-token-usage): a turn requeued onto a restarted worker is a
|
|
448
|
+
// second run of it.
|
|
449
|
+
const tokenRunId = randomUUID();
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Ship the turn's tokens to the token booking: the SDK's usage
|
|
453
|
+
* for the turn, all its model calls together, under the turn's model.
|
|
454
|
+
* Cursor counts fresh input apart from the cache (cursor-context-usage.mjs
|
|
455
|
+
* adds the three up). No cost: the SDK prices no single turn. A field the
|
|
456
|
+
* SDK left out stays out, and the relay keeps it unknown. Fire-and-forget:
|
|
457
|
+
* it never throws and the turn does not wait on it.
|
|
458
|
+
*/
|
|
459
|
+
async function publishTurnTokens(message, state, model, normalizer) {
|
|
460
|
+
try {
|
|
461
|
+
const usage = state.lastUsage || normalizer?.lastUsage || null;
|
|
462
|
+
const turnModel = state.responseModel || model || '';
|
|
463
|
+
if (!usage || typeof usage !== 'object' || !turnModel || !message?.id) return;
|
|
464
|
+
await api('POST', '/api/turn-token-usage', {
|
|
465
|
+
conversationId: message.conversationId,
|
|
466
|
+
turnId: String(message.id),
|
|
467
|
+
runId: message.attemptId || tokenRunId,
|
|
468
|
+
provider: 'cursor',
|
|
469
|
+
...(agentHandle?.agentId ? { nativeSessionId: agentHandle.agentId } : {}),
|
|
470
|
+
at: new Date().toISOString(),
|
|
471
|
+
models: [{
|
|
472
|
+
model: turnModel,
|
|
473
|
+
inputTokens: usage.inputTokens,
|
|
474
|
+
outputTokens: usage.outputTokens,
|
|
475
|
+
cacheWriteTokens: usage.cacheWriteTokens,
|
|
476
|
+
cacheReadTokens: usage.cacheReadTokens,
|
|
477
|
+
reasoningTokens: usage.reasoningTokens,
|
|
478
|
+
}],
|
|
479
|
+
});
|
|
480
|
+
} catch (error) {
|
|
481
|
+
dbg('turn token publish failed', error?.message || String(error));
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
450
485
|
/**
|
|
451
486
|
* Report the agent's cumulative billed usage so the relay can book this
|
|
452
487
|
* turn's increase against the configured monthly allowance. The model is
|
|
@@ -704,6 +739,7 @@ export function createCursorTurnRunner({
|
|
|
704
739
|
// (the auth retry swaps it), so a discarded attempt's usage is not
|
|
705
740
|
// republished over the retry's.
|
|
706
741
|
await publishContextUsage(message, state, model, normalizer);
|
|
742
|
+
void publishTurnTokens(message, state, model, normalizer);
|
|
707
743
|
await publishPlanUsage(message, state, model);
|
|
708
744
|
}
|
|
709
745
|
|
package/server/db-schema.mjs
CHANGED
|
@@ -40,6 +40,8 @@ import { migrateQueueProvider } from './migrations/0031-queue-provider.mjs';
|
|
|
40
40
|
import { migrateRuntimeSessionBillingSwitch } from './migrations/0032-runtime-session-billing-switch.mjs';
|
|
41
41
|
import { migrateClaudeApiUsageCheckpoints } from './migrations/0033-claude-api-usage-checkpoints.mjs';
|
|
42
42
|
import { migrateClaudeApiUsageTotals } from './migrations/0034-claude-api-usage-totals.mjs';
|
|
43
|
+
import { migrateConversationPlanOnly } from './migrations/0035-conversation-plan-only.mjs';
|
|
44
|
+
import { migrateTokenUsage } from './migrations/0036-token-usage.mjs';
|
|
43
45
|
import { migrateImageConversationSchema } from './repositories/image-conversation-repository.mjs';
|
|
44
46
|
|
|
45
47
|
// Mirrors DEFAULT_RELAY_MODE in server-runtime.mjs; used only by the one-time
|
|
@@ -1059,6 +1061,22 @@ try {
|
|
|
1059
1061
|
console.warn(`[claude-api-usage] migration 0034 failed; retrying on next boot: ${error?.message || error}`);
|
|
1060
1062
|
}
|
|
1061
1063
|
|
|
1064
|
+
// A plan-only conversation, handled like 0020.
|
|
1065
|
+
try {
|
|
1066
|
+
migrateConversationPlanOnly(db);
|
|
1067
|
+
} catch (error) {
|
|
1068
|
+
console.warn(`[plan-only] migration 0035 failed; retrying on next boot: ${error?.message || error}`);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// Tokens per turn and per day for every provider, with a one-time
|
|
1072
|
+
// copy of the Claude API and DeepSeek days. After 0025 and 0034: a missing
|
|
1073
|
+
// source throws here, so it is logged and retried next boot like 0034.
|
|
1074
|
+
try {
|
|
1075
|
+
migrateTokenUsage(db);
|
|
1076
|
+
} catch (error) {
|
|
1077
|
+
console.warn(`[token-usage] migration 0036 failed; retrying on next boot: ${error?.message || error}`);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1062
1080
|
const conversationColumns = db.prepare(`PRAGMA table_info(conversations)`).all().map((c) => c.name);
|
|
1063
1081
|
if (!conversationColumns.includes('archived')) {
|
|
1064
1082
|
db.exec(`ALTER TABLE conversations ADD COLUMN archived INTEGER NOT NULL DEFAULT 0`);
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
// (api-docs.deepseek.com, quick_start/agent_integrations/claude_code).
|
|
36
36
|
|
|
37
37
|
import { deepseekAnthropicBaseUrl } from '../../shared/deepseek-api.mjs';
|
|
38
|
+
import { estimateCost } from '../../shared/deepseek-pricing.mjs';
|
|
38
39
|
import { isHostClaudeSessionVariable } from '../../shared/host-claude-session-env.mjs';
|
|
39
40
|
import { CLAUDE_CLI_KEY_CHAT_SETTINGS, claudeCliPinnedEnv } from '../claude-worker/claude-cli-pinned-env.mjs';
|
|
40
41
|
import {
|
|
@@ -262,6 +263,19 @@ export function createDeepSeekClaudeBackend({ env = process.env } = {}) {
|
|
|
262
263
|
// relay's model ids (relayModel); the relay books what each report adds.
|
|
263
264
|
// Claude Code's `total_cost_usd` is at Claude's prices and is not sent.
|
|
264
265
|
usageRoute: '/api/deepseek-plan-usage',
|
|
266
|
+
// A turn's tokens (POST /api/turn-token-usage) are priced at DeepSeek's
|
|
267
|
+
// own rates for the time, as the card's estimate is: cache writes and
|
|
268
|
+
// fresh input are both misses. Null for a model the table has no price for.
|
|
269
|
+
turnTokenCost: (entry, at) => {
|
|
270
|
+
const costUsd = estimateCost({
|
|
271
|
+
model: entry.model,
|
|
272
|
+
at,
|
|
273
|
+
hitTokens: entry.cacheReadTokens,
|
|
274
|
+
missTokens: entry.inputTokens + entry.cacheWriteTokens,
|
|
275
|
+
outputTokens: entry.outputTokens,
|
|
276
|
+
});
|
|
277
|
+
return { costUsd, costSource: costUsd === null ? null : 'oar-deepseek-table' };
|
|
278
|
+
},
|
|
265
279
|
|
|
266
280
|
// A Claude id is not a model of DeepSeek's to request, only one it may
|
|
267
281
|
// have served (relayModel).
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
1
2
|
import {
|
|
2
3
|
createGrokAgentHandle,
|
|
3
4
|
startGrokTurn,
|
|
@@ -11,15 +12,10 @@ import { createPreviewInstructionsProvider } from '../../shared/preview-instruct
|
|
|
11
12
|
import { fetchRemoteRelayInflight } from '../../shared/remote-relay-tool-core.mjs';
|
|
12
13
|
import { renderMediaEmbedInstructionBlock } from '../../shared/media-embed-instructions.mjs';
|
|
13
14
|
import { countPlanLikeLines } from '../../shared/plan-lines.mjs';
|
|
15
|
+
import { PLAN_BOARD_ACTIONS } from '../../shared/plan-board-actions.mjs';
|
|
14
16
|
import { EMPTY_TURN_COMPLETION_NOTE } from '../../shared/empty-turn-completion.mjs';
|
|
15
17
|
import { explainLaunchFailure, missingWorkingDirectoryError } from '../../shared/worker-runtime/working-directory.mjs';
|
|
16
18
|
|
|
17
|
-
const PLAN_BOARD_ACTIONS = [
|
|
18
|
-
{ id: 'autopilot', label: 'Implement in autopilot', mode: 'autopilot' },
|
|
19
|
-
{ id: 'interactive', label: 'Stop here and prompt myself', mode: 'agent' },
|
|
20
|
-
{ id: 'exit_only', label: 'Stop here', mode: 'agent' },
|
|
21
|
-
];
|
|
22
|
-
|
|
23
19
|
// Grok has no exit-plan tool, so the only board source is the plan-mode
|
|
24
20
|
// text-shape fallback; the payload matches the Claude/Cursor workers'.
|
|
25
21
|
export function buildGrokPlanReadyBoardPayload({
|
|
@@ -352,6 +348,47 @@ export function createGrokTurnRunner({
|
|
|
352
348
|
});
|
|
353
349
|
}
|
|
354
350
|
|
|
351
|
+
// A process without the row's attempt id names its run once (POST
|
|
352
|
+
// /api/turn-token-usage): a turn requeued onto a restarted worker is a
|
|
353
|
+
// second run of it.
|
|
354
|
+
const tokenRunId = randomUUID();
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Ship the turn's tokens to the token booking: per model where
|
|
358
|
+
* the agent breaks them down, else the turn's totals under its model, with
|
|
359
|
+
* the agent's own cost. Grok counts fresh input apart from the cache
|
|
360
|
+
* (grok-context-usage.mjs adds the three up). Advisory, fire-and-forget.
|
|
361
|
+
*/
|
|
362
|
+
async function publishTurnTokens(message, state, model) {
|
|
363
|
+
const usage = state.turnUsage;
|
|
364
|
+
if (!usage || !message?.id) return;
|
|
365
|
+
const entry = (source, entryModel) => ({
|
|
366
|
+
model: entryModel,
|
|
367
|
+
inputTokens: source.inputTokens,
|
|
368
|
+
outputTokens: source.outputTokens,
|
|
369
|
+
cacheWriteTokens: source.cacheCreationTokens,
|
|
370
|
+
cacheReadTokens: source.cachedReadTokens,
|
|
371
|
+
reasoningTokens: source.reasoningTokens,
|
|
372
|
+
...(source.costUsd !== null && source.costUsd !== undefined ? { costUsd: source.costUsd, costSource: 'provider' } : {}),
|
|
373
|
+
});
|
|
374
|
+
const turnModel = state.responseModel || usage.modelId || model || '';
|
|
375
|
+
const models = Array.isArray(usage.modelUsage) && usage.modelUsage.length
|
|
376
|
+
? usage.modelUsage.map((item) => entry(item, item.model))
|
|
377
|
+
: (turnModel ? [entry(usage, turnModel)] : []);
|
|
378
|
+
if (!models.length) return;
|
|
379
|
+
await api('POST', '/api/turn-token-usage', {
|
|
380
|
+
conversationId: message.conversationId,
|
|
381
|
+
turnId: String(message.id),
|
|
382
|
+
runId: message.attemptId || tokenRunId,
|
|
383
|
+
provider: 'grok',
|
|
384
|
+
...(sdkSessionId ? { nativeSessionId: sdkSessionId } : {}),
|
|
385
|
+
at: usage.capturedAt || new Date().toISOString(),
|
|
386
|
+
models,
|
|
387
|
+
}).catch((error) => {
|
|
388
|
+
dbg('turn token publish failed', error?.message || String(error));
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
355
392
|
/**
|
|
356
393
|
* Ship per-turn token/cost totals for the Check Usage Grok card. Advisory:
|
|
357
394
|
* failures never disturb the turn reply.
|
|
@@ -521,6 +558,7 @@ export function createGrokTurnRunner({
|
|
|
521
558
|
// awaits made it false.
|
|
522
559
|
void publishContextUsage(message, state, model).catch(() => {});
|
|
523
560
|
void publishPlanUsage(message, state).catch(() => {});
|
|
561
|
+
void publishTurnTokens(message, state, model).catch(() => {});
|
|
524
562
|
}
|
|
525
563
|
|
|
526
564
|
if (aborted) {
|
|
@@ -12,13 +12,14 @@
|
|
|
12
12
|
* Safe to run repeatedly (idempotent) and against a live database — ADD COLUMN
|
|
13
13
|
* only takes a brief write lock and new columns default to NULL.
|
|
14
14
|
*
|
|
15
|
-
* Usage: node server/migrations/0001-add-structured-answer.mjs [path/to/
|
|
15
|
+
* Usage: node server/migrations/0001-add-structured-answer.mjs [path/to/oar.db]
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { fileURLToPath } from 'url';
|
|
19
19
|
import path from 'path';
|
|
20
20
|
import process from 'process';
|
|
21
21
|
import Database from 'better-sqlite3';
|
|
22
|
+
import { cliDbPath } from '../services/database-file.mjs';
|
|
22
23
|
|
|
23
24
|
const NEW_COLUMNS = [
|
|
24
25
|
{ name: 'structured_answer', ddl: 'ALTER TABLE relay_questions ADD COLUMN structured_answer TEXT' },
|
|
@@ -56,15 +57,12 @@ export function migrate(dbPath) {
|
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
function defaultDbPath() {
|
|
60
|
-
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
61
|
-
return path.resolve(here, '..', 'data', 'copilot.db');
|
|
62
|
-
}
|
|
63
|
-
|
|
64
60
|
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
65
61
|
if (isMain) {
|
|
66
|
-
const dbPath = process.argv[2] ? path.resolve(process.argv[2]) : defaultDbPath();
|
|
67
62
|
try {
|
|
63
|
+
// The given path, else the database in server/data (oar.db, else the
|
|
64
|
+
// name before 0.9.20); never a new one.
|
|
65
|
+
const dbPath = cliDbPath(process.argv[2], new URL('..', import.meta.url));
|
|
68
66
|
const result = migrate(dbPath);
|
|
69
67
|
const applied = result.applied.length ? result.applied.join(', ') : '(none)';
|
|
70
68
|
const skipped = result.skipped.length ? result.skipped.join(', ') : '(none)';
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*
|
|
19
19
|
* Idempotent: re-running once path_key exists is a no-op.
|
|
20
20
|
*
|
|
21
|
-
* Usage: node server/migrations/0002-recent-workspace-roots-path-key.mjs [path/to/
|
|
21
|
+
* Usage: node server/migrations/0002-recent-workspace-roots-path-key.mjs [path/to/oar.db]
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { fileURLToPath } from 'url';
|
|
@@ -27,6 +27,7 @@ import process from 'process';
|
|
|
27
27
|
import Database from 'better-sqlite3';
|
|
28
28
|
|
|
29
29
|
import { normalizeWorkspaceRootKey } from '../services/workspace-root-path-policy.mjs';
|
|
30
|
+
import { cliDbPath } from '../services/database-file.mjs';
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* Rebuild against an already-open database handle.
|
|
@@ -97,15 +98,12 @@ export function migrate(dbPath, options = {}) {
|
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
100
|
|
|
100
|
-
function defaultDbPath() {
|
|
101
|
-
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
102
|
-
return path.resolve(here, '..', 'data', 'copilot.db');
|
|
103
|
-
}
|
|
104
|
-
|
|
105
101
|
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
106
102
|
if (isMain) {
|
|
107
|
-
const dbPath = process.argv[2] ? path.resolve(process.argv[2]) : defaultDbPath();
|
|
108
103
|
try {
|
|
104
|
+
// The given path, else the database in server/data (oar.db, else the
|
|
105
|
+
// name before 0.9.20); never a new one.
|
|
106
|
+
const dbPath = cliDbPath(process.argv[2], new URL('..', import.meta.url));
|
|
109
107
|
const result = migrate(dbPath);
|
|
110
108
|
console.log(`Migration 0002 on ${dbPath}`);
|
|
111
109
|
if (result.applied) {
|
|
@@ -14,13 +14,14 @@
|
|
|
14
14
|
* databases; this module exists so existing databases upgrade at startup and
|
|
15
15
|
* so the migration can be run offline against a db file.
|
|
16
16
|
*
|
|
17
|
-
* Usage: node server/migrations/0003-push-subscriptions.mjs [path/to/
|
|
17
|
+
* Usage: node server/migrations/0003-push-subscriptions.mjs [path/to/oar.db]
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { fileURLToPath } from 'url';
|
|
21
21
|
import path from 'path';
|
|
22
22
|
import process from 'process';
|
|
23
23
|
import Database from 'better-sqlite3';
|
|
24
|
+
import { cliDbPath } from '../services/database-file.mjs';
|
|
24
25
|
|
|
25
26
|
export const PUSH_SUBSCRIPTIONS_SCHEMA = `
|
|
26
27
|
CREATE TABLE IF NOT EXISTS push_subscriptions (
|
|
@@ -67,15 +68,12 @@ export function migrate(dbPath) {
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
function defaultDbPath() {
|
|
71
|
-
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
72
|
-
return path.resolve(here, '..', 'data', 'copilot.db');
|
|
73
|
-
}
|
|
74
|
-
|
|
75
71
|
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
76
72
|
if (isMain) {
|
|
77
|
-
const dbPath = process.argv[2] ? path.resolve(process.argv[2]) : defaultDbPath();
|
|
78
73
|
try {
|
|
74
|
+
// The given path, else the database in server/data (oar.db, else the
|
|
75
|
+
// name before 0.9.20); never a new one.
|
|
76
|
+
const dbPath = cliDbPath(process.argv[2], new URL('..', import.meta.url));
|
|
79
77
|
const result = migrate(dbPath);
|
|
80
78
|
console.log(`Migration 0003 on ${dbPath}`);
|
|
81
79
|
console.log(result.applied ? ' created push_subscriptions' : ` skipped: ${result.reason}`);
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* Idempotent: the column is added once, the backfill only fills NULLs, and the
|
|
21
21
|
* kind rewrite matches nothing once applied. Safe to run on every boot.
|
|
22
22
|
*
|
|
23
|
-
* Usage: node server/migrations/0004-steer-settle-markers.mjs [path/to/
|
|
23
|
+
* Usage: node server/migrations/0004-steer-settle-markers.mjs [path/to/oar.db]
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import { fileURLToPath } from 'url';
|
|
@@ -29,6 +29,7 @@ import process from 'process';
|
|
|
29
29
|
import Database from 'better-sqlite3';
|
|
30
30
|
|
|
31
31
|
import { STEER_FOLDED_TEXT } from '../../shared/steer-settle-markers.mjs';
|
|
32
|
+
import { cliDbPath } from '../services/database-file.mjs';
|
|
32
33
|
|
|
33
34
|
function tableExists(db, name) {
|
|
34
35
|
return !!db
|
|
@@ -97,15 +98,12 @@ export function migrate(dbPath) {
|
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
100
|
|
|
100
|
-
function defaultDbPath() {
|
|
101
|
-
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
102
|
-
return path.resolve(here, '..', 'data', 'copilot.db');
|
|
103
|
-
}
|
|
104
|
-
|
|
105
101
|
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
106
102
|
if (isMain) {
|
|
107
|
-
const dbPath = process.argv[2] ? path.resolve(process.argv[2]) : defaultDbPath();
|
|
108
103
|
try {
|
|
104
|
+
// The given path, else the database in server/data (oar.db, else the
|
|
105
|
+
// name before 0.9.20); never a new one.
|
|
106
|
+
const dbPath = cliDbPath(process.argv[2], new URL('..', import.meta.url));
|
|
109
107
|
const result = migrate(dbPath);
|
|
110
108
|
console.log(`Migration 0004 on ${dbPath}`);
|
|
111
109
|
console.log(result.applied
|
|
@@ -15,13 +15,14 @@
|
|
|
15
15
|
*
|
|
16
16
|
* Additive and idempotent. Safe to run on every boot.
|
|
17
17
|
*
|
|
18
|
-
* Usage: node server/migrations/0005-remote-relays.mjs [path/to/
|
|
18
|
+
* Usage: node server/migrations/0005-remote-relays.mjs [path/to/oar.db]
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { fileURLToPath } from 'url';
|
|
22
22
|
import path from 'path';
|
|
23
23
|
import process from 'process';
|
|
24
24
|
import Database from 'better-sqlite3';
|
|
25
|
+
import { cliDbPath } from '../services/database-file.mjs';
|
|
25
26
|
|
|
26
27
|
function tableExists(db, name) {
|
|
27
28
|
return !!db
|
|
@@ -68,15 +69,12 @@ export function migrate(dbPath) {
|
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
function defaultDbPath() {
|
|
72
|
-
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
73
|
-
return path.resolve(here, '..', 'data', 'copilot.db');
|
|
74
|
-
}
|
|
75
|
-
|
|
76
72
|
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
77
73
|
if (isMain) {
|
|
78
|
-
const dbPath = process.argv[2] ? path.resolve(process.argv[2]) : defaultDbPath();
|
|
79
74
|
try {
|
|
75
|
+
// The given path, else the database in server/data (oar.db, else the
|
|
76
|
+
// name before 0.9.20); never a new one.
|
|
77
|
+
const dbPath = cliDbPath(process.argv[2], new URL('..', import.meta.url));
|
|
80
78
|
const result = migrate(dbPath);
|
|
81
79
|
console.log(`Migration 0005 on ${dbPath}`);
|
|
82
80
|
console.log(result.applied
|
|
@@ -11,13 +11,14 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Additive and idempotent. Safe to run on every boot.
|
|
13
13
|
*
|
|
14
|
-
* Usage: node server/migrations/0006-workspace-root-settings.mjs [path/to/
|
|
14
|
+
* Usage: node server/migrations/0006-workspace-root-settings.mjs [path/to/oar.db]
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { fileURLToPath } from 'url';
|
|
18
18
|
import path from 'path';
|
|
19
19
|
import process from 'process';
|
|
20
20
|
import Database from 'better-sqlite3';
|
|
21
|
+
import { cliDbPath } from '../services/database-file.mjs';
|
|
21
22
|
|
|
22
23
|
function tableExists(db, name) {
|
|
23
24
|
return !!db
|
|
@@ -38,11 +39,6 @@ export function migrateWorkspaceRootSettings(db) {
|
|
|
38
39
|
return { applied: !existed, tableCreated: !existed };
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
function defaultDbPath() {
|
|
42
|
-
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
43
|
-
return path.resolve(here, '..', 'data', 'copilot.db');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
42
|
export function migrate(dbPath, options = {}) {
|
|
47
43
|
const db = new Database(dbPath, options);
|
|
48
44
|
try {
|
|
@@ -54,8 +50,10 @@ export function migrate(dbPath, options = {}) {
|
|
|
54
50
|
|
|
55
51
|
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
56
52
|
if (isMain) {
|
|
57
|
-
const dbPath = process.argv[2] ? path.resolve(process.argv[2]) : defaultDbPath();
|
|
58
53
|
try {
|
|
54
|
+
// The given path, else the database in server/data (oar.db, else the
|
|
55
|
+
// name before 0.9.20); never a new one.
|
|
56
|
+
const dbPath = cliDbPath(process.argv[2], new URL('..', import.meta.url));
|
|
59
57
|
const result = migrate(dbPath);
|
|
60
58
|
console.log(`Migration 0006 on ${dbPath}`);
|
|
61
59
|
console.log(result.applied ? ' workspace_root_settings table created' : ' skipped: nothing to change');
|
|
@@ -17,13 +17,14 @@
|
|
|
17
17
|
*
|
|
18
18
|
* Additive and idempotent. Safe to run on every boot.
|
|
19
19
|
*
|
|
20
|
-
* Usage: node server/migrations/0007-claude-cloud.mjs [path/to/
|
|
20
|
+
* Usage: node server/migrations/0007-claude-cloud.mjs [path/to/oar.db]
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
import { fileURLToPath } from 'url';
|
|
24
24
|
import path from 'path';
|
|
25
25
|
import process from 'process';
|
|
26
26
|
import Database from 'better-sqlite3';
|
|
27
|
+
import { cliDbPath } from '../services/database-file.mjs';
|
|
27
28
|
|
|
28
29
|
const COLUMNS = Object.freeze([
|
|
29
30
|
Object.freeze({ table: 'runtime_sessions', name: 'claude_cloud_session_id', type: 'TEXT' }),
|
|
@@ -53,11 +54,6 @@ export function migrateClaudeCloud(db) {
|
|
|
53
54
|
return { applied: columnsAdded > 0, columnsAdded };
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
function defaultDbPath() {
|
|
57
|
-
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
58
|
-
return path.resolve(here, '..', 'data', 'copilot.db');
|
|
59
|
-
}
|
|
60
|
-
|
|
61
57
|
export function migrate(dbPath, options = {}) {
|
|
62
58
|
const db = new Database(dbPath, options);
|
|
63
59
|
try {
|
|
@@ -69,8 +65,10 @@ export function migrate(dbPath, options = {}) {
|
|
|
69
65
|
|
|
70
66
|
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
71
67
|
if (isMain) {
|
|
72
|
-
const dbPath = process.argv[2] ? path.resolve(process.argv[2]) : defaultDbPath();
|
|
73
68
|
try {
|
|
69
|
+
// The given path, else the database in server/data (oar.db, else the
|
|
70
|
+
// name before 0.9.20); never a new one.
|
|
71
|
+
const dbPath = cliDbPath(process.argv[2], new URL('..', import.meta.url));
|
|
74
72
|
const result = migrate(dbPath);
|
|
75
73
|
console.log(`Migration 0007 on ${dbPath}`);
|
|
76
74
|
console.log(result.applied ? ` claude cloud columns added: ${result.columnsAdded}` : ' skipped: nothing to change');
|
|
@@ -11,13 +11,14 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Additive and idempotent. Safe to run on every boot.
|
|
13
13
|
*
|
|
14
|
-
* Usage: node server/migrations/0008-message-pins.mjs [path/to/
|
|
14
|
+
* Usage: node server/migrations/0008-message-pins.mjs [path/to/oar.db]
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { fileURLToPath } from 'url';
|
|
18
18
|
import path from 'path';
|
|
19
19
|
import process from 'process';
|
|
20
20
|
import Database from 'better-sqlite3';
|
|
21
|
+
import { cliDbPath } from '../services/database-file.mjs';
|
|
21
22
|
|
|
22
23
|
function tableExists(db, name) {
|
|
23
24
|
return !!db
|
|
@@ -54,11 +55,6 @@ export function migrateMessagePins(db) {
|
|
|
54
55
|
return { applied: columnsAdded + indexesAdded > 0, columnsAdded, indexesAdded };
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
function defaultDbPath() {
|
|
58
|
-
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
59
|
-
return path.resolve(here, '..', 'data', 'copilot.db');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
58
|
export function migrate(dbPath, options = {}) {
|
|
63
59
|
const db = new Database(dbPath, options);
|
|
64
60
|
try {
|
|
@@ -70,8 +66,10 @@ export function migrate(dbPath, options = {}) {
|
|
|
70
66
|
|
|
71
67
|
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
72
68
|
if (isMain) {
|
|
73
|
-
const dbPath = process.argv[2] ? path.resolve(process.argv[2]) : defaultDbPath();
|
|
74
69
|
try {
|
|
70
|
+
// The given path, else the database in server/data (oar.db, else the
|
|
71
|
+
// name before 0.9.20); never a new one.
|
|
72
|
+
const dbPath = cliDbPath(process.argv[2], new URL('..', import.meta.url));
|
|
75
73
|
const result = migrate(dbPath);
|
|
76
74
|
console.log(`Migration 0008 on ${dbPath}`);
|
|
77
75
|
console.log(result.applied
|