@north-light/crouter 0.3.220 → 0.3.222
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/dist/api/client.d.ts +29 -0
- package/dist/api/client.js +44 -0
- package/dist/api/dto/chat-inventory.d.ts +56 -0
- package/dist/api/dto/chat-inventory.js +11 -0
- package/dist/api/dto/human-requests.d.ts +88 -0
- package/dist/api/dto/human-requests.js +4 -0
- package/dist/api/dto/human.d.ts +3 -0
- package/dist/api/dto/profiles.d.ts +19 -5
- package/dist/api/dto/profiles.js +2 -1
- package/dist/api/dto/reviews.d.ts +2 -0
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +2 -0
- package/dist/api/routes.d.ts +8 -0
- package/dist/api/routes.js +11 -0
- package/dist/build-root.d.ts +2 -6
- package/dist/build-root.js +51 -4
- package/dist/builtin-memory/00-runtime-base/00-authoring.md +31 -0
- package/dist/builtin-memory/00-runtime-base/01-escalation.md +14 -0
- package/dist/builtin-memory/{insights/listen.md → 00-runtime-base/02-insight-capture.md} +1 -0
- package/dist/builtin-memory/02-turn-lifecycle/00-ending-a-turn.md +27 -0
- package/dist/builtin-memory/{02-lifecycle/01-resident.md → 02-turn-lifecycle/02-resident.md} +5 -0
- package/dist/builtin-memory/04-base-worker.md +4 -8
- package/dist/builtin-memory/04-orchestration-kernel.md +1 -1
- package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +1 -0
- package/dist/builtin-memory/05-kinds/advisor/advice-contract.md +1 -0
- package/dist/builtin-memory/05-kinds/design/00-base.md +2 -1
- package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +2 -1
- package/dist/builtin-memory/05-kinds/design/design-contract.md +19 -0
- package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -0
- package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +1 -0
- package/dist/builtin-memory/05-kinds/explore/00-base.md +1 -0
- package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +1 -0
- package/dist/builtin-memory/05-kinds/general/00-base.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/00-base.md +2 -1
- package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +2 -1
- package/dist/builtin-memory/05-kinds/plan/plan-contract.md +28 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/lens-contract.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +1 -0
- package/dist/builtin-memory/05-kinds/review/00-base.md +1 -0
- package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -0
- package/dist/builtin-memory/05-kinds/review/companion/00-base.md +1 -0
- package/dist/builtin-memory/05-kinds/review/security-findings.md +1 -0
- package/dist/builtin-memory/05-kinds/spec/00-base.md +4 -3
- package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +1 -0
- package/dist/builtin-memory/05-kinds/spec/requirements.md +1 -0
- package/dist/builtin-memory/design/guide.md +35 -0
- package/dist/builtin-memory/design/roadmap.md +21 -0
- package/dist/builtin-memory/insights/capture.md +1 -1
- package/dist/builtin-memory/internal/agent-shaping.md +3 -1
- package/dist/builtin-memory/internal/memory-loading.md +4 -0
- package/dist/builtin-memory/internal/plugins.md +10 -1
- package/dist/builtin-memory/internal/storage-tiers.md +1 -1
- package/dist/builtin-memory/plan/roadmap.md +6 -22
- package/dist/builtin-memory/spec/guide.md +23 -6
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-commands/index.ts +7 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +34 -16
- package/dist/clients/attach/__tests__/ref-autocomplete.test.js +1 -1
- package/dist/clients/attach/__tests__/titled-editor-preview.test.js +1 -1
- package/dist/clients/attach/overlays/file-review.js +2 -2
- package/dist/clients/attach/render/markdown-source.js +106 -1
- package/dist/clients/attach/session/file-links.d.ts +13 -4
- package/dist/clients/attach/session/file-links.js +54 -58
- package/dist/clients/attach/session/keys.d.ts +1 -1
- package/dist/clients/attach/session/profile-files.js +1 -1
- package/dist/clients/attach/viewer.js +698 -696
- package/dist/clients/inbox/controller.js +1 -1
- package/dist/clients/inbox/resolve.d.ts +1 -0
- package/dist/clients/inbox/review/launch.d.ts +8 -4
- package/dist/clients/inbox/review/launch.js +55 -5
- package/dist/clients/inbox/review/review-client.d.ts +1 -0
- package/dist/clients/inbox/review/review-client.js +7 -1
- package/dist/clients/inbox/review-adapter.d.ts +1 -8
- package/dist/clients/inbox/review-adapter.js +4 -52
- package/dist/commands/__tests__/human.test.js +2 -2
- package/dist/commands/human/request.d.ts +2 -0
- package/dist/commands/human/request.js +281 -0
- package/dist/commands/human.js +5 -2
- package/dist/commands/memory/lint.js +2 -1
- package/dist/commands/memory/read.js +1 -0
- package/dist/commands/memory.js +1 -1
- package/dist/commands/pkg/market-manage.js +165 -75
- package/dist/commands/pkg/plugin-inspect.js +19 -2
- package/dist/commands/pkg/plugin-manage.d.ts +8 -3
- package/dist/commands/pkg/plugin-manage.js +72 -24
- package/dist/commands/profile/default.js +6 -10
- package/dist/commands/profile/list.js +5 -3
- package/dist/commands/profile/new.js +21 -8
- package/dist/commands/profile/project.js +25 -19
- package/dist/commands/profile/show.js +3 -3
- package/dist/commands/surface-inbox.js +1 -0
- package/dist/commands/sys/__tests__/migrate.test.js +16 -5
- package/dist/commands/sys/config.js +2 -2
- package/dist/commands/sys/doctor.js +87 -5
- package/dist/commands/sys/migrate.js +38 -19
- package/dist/commands/sys/setup-core.js +1 -1
- package/dist/commands/sys/sync-project-guidance.js +1 -1
- package/dist/core/__tests__/broker-extension-canvas-db-boundary.test.js +7 -4
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +1 -1
- package/dist/core/__tests__/fixtures/c5-command-boundary-ext.js +24 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +24 -18
- package/dist/core/__tests__/fixtures/fake-engine.js +8 -1
- package/dist/core/__tests__/fixtures/memory-slash-live-probe.js +71 -0
- package/dist/core/__tests__/human-action-delivery.test.d.ts +1 -0
- package/dist/core/__tests__/human-action-delivery.test.js +140 -0
- package/dist/core/__tests__/human-actions.test.d.ts +1 -0
- package/dist/core/__tests__/human-actions.test.js +116 -0
- package/dist/core/__tests__/inline-memory-refs.test.js +36 -2
- package/dist/core/__tests__/profile-project-memory-delivery.test.d.ts +1 -0
- package/dist/core/__tests__/profile-project-memory-delivery.test.js +217 -0
- package/dist/core/__tests__/prospective-inventory-capability-parity.test.d.ts +1 -0
- package/dist/core/__tests__/prospective-inventory-capability-parity.test.js +91 -0
- package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.d.ts +1 -0
- package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.js +127 -0
- package/dist/core/__tests__/seam/prospective-inventory-stdout.test.d.ts +1 -0
- package/dist/core/__tests__/seam/prospective-inventory-stdout.test.js +31 -0
- package/dist/core/__tests__/serial/broker-sdk-wiring.test.js +102 -2
- package/dist/core/bootstrap.js +6 -0
- package/dist/core/canvas/browse/app.js +5 -2
- package/dist/core/canvas/browse/model.d.ts +25 -15
- package/dist/core/canvas/browse/model.js +86 -65
- package/dist/core/canvas/db.js +23 -0
- package/dist/core/canvas/human-deliveries.d.ts +53 -0
- package/dist/core/canvas/human-deliveries.js +75 -0
- package/dist/core/canvas/render-source.d.ts +6 -0
- package/dist/core/canvas/render-source.js +7 -1
- package/dist/core/canvas/render.js +10 -2
- package/dist/core/command-hooks/artifact.d.ts +10 -0
- package/dist/core/command-hooks/artifact.js +129 -0
- package/dist/core/command-hooks/catalog.d.ts +14 -0
- package/dist/core/command-hooks/catalog.js +38 -0
- package/dist/core/command-hooks/compose.d.ts +15 -0
- package/dist/core/command-hooks/compose.js +99 -0
- package/dist/core/command-hooks/discovery.d.ts +87 -0
- package/dist/core/command-hooks/discovery.js +174 -0
- package/dist/core/command-hooks/help.d.ts +5 -0
- package/dist/core/command-hooks/help.js +18 -0
- package/dist/core/command-hooks/index.d.ts +6 -0
- package/dist/core/command-hooks/index.js +6 -0
- package/dist/core/command-hooks/report.d.ts +23 -0
- package/dist/core/command-hooks/report.js +19 -0
- package/dist/core/command-hooks/schema.d.ts +27 -0
- package/dist/core/command-hooks/schema.js +68 -0
- package/dist/core/command-hooks/transport/exec-invoke.d.ts +22 -0
- package/dist/core/command-hooks/transport/exec-invoke.js +274 -0
- package/dist/core/command-plugins/presence.d.ts +2 -0
- package/dist/core/command-plugins/presence.js +17 -0
- package/dist/core/command-plugins/transport/exec-invoke.d.ts +5 -0
- package/dist/core/command-plugins/transport/exec-invoke.js +58 -5
- package/dist/core/command.d.ts +8 -1
- package/dist/core/command.js +12 -10
- package/dist/core/config.d.ts +13 -1
- package/dist/core/config.js +51 -1
- package/dist/core/feed/inbox.d.ts +6 -0
- package/dist/core/feed/inbox.js +9 -1
- package/dist/core/help.d.ts +7 -1
- package/dist/core/human/action-binding.d.ts +21 -0
- package/dist/core/human/action-binding.js +40 -0
- package/dist/core/human/completion.d.ts +38 -0
- package/dist/core/human/completion.js +27 -0
- package/dist/core/human/convention.d.ts +2 -0
- package/dist/core/human/convention.js +2 -0
- package/dist/core/human/tickets.d.ts +25 -6
- package/dist/core/human/tickets.js +19 -13
- package/dist/core/human/types.d.ts +5 -0
- package/dist/core/human-actions.d.ts +25 -0
- package/dist/core/human-actions.js +101 -0
- package/dist/core/io.d.ts +9 -1
- package/dist/core/io.js +44 -2
- package/dist/core/memory/inline-ref-inventory.d.ts +2 -1
- package/dist/core/memory/inline-ref-inventory.js +15 -8
- package/dist/core/memory-resolver.d.ts +13 -1
- package/dist/core/memory-resolver.js +26 -20
- package/dist/core/profiles/manifest.d.ts +13 -2
- package/dist/core/profiles/manifest.js +84 -18
- package/dist/core/profiles/select.d.ts +2 -0
- package/dist/core/profiles/select.js +29 -12
- package/dist/core/render.js +11 -0
- package/dist/core/runtime/advertised-command-invocation.d.ts +20 -0
- package/dist/core/runtime/advertised-command-invocation.js +233 -0
- package/dist/core/runtime/bearings.js +1 -1
- package/dist/core/runtime/broker/event-projection.js +7 -0
- package/dist/core/runtime/broker/frame-dispatch.d.ts +1 -1
- package/dist/core/runtime/broker/frame-dispatch.js +13 -11
- package/dist/core/runtime/broker/read-ops.d.ts +4 -0
- package/dist/core/runtime/broker/read-ops.js +6 -2
- package/dist/core/runtime/broker-extension-render.js +1 -1
- package/dist/core/runtime/broker-inventory.d.ts +4 -0
- package/dist/core/runtime/broker-inventory.js +116 -0
- package/dist/core/runtime/broker-persona-guidance.js +1 -1
- package/dist/core/runtime/broker-protocol.d.ts +9 -2
- package/dist/core/runtime/broker.js +10 -1
- package/dist/core/runtime/chat-inventory-rows.d.ts +8 -0
- package/dist/core/runtime/chat-inventory-rows.js +105 -0
- package/dist/core/runtime/command-surface.d.ts +38 -0
- package/dist/core/runtime/command-surface.js +117 -0
- package/dist/core/runtime/launch-target.d.ts +25 -0
- package/dist/core/runtime/launch-target.js +54 -0
- package/dist/core/runtime/node-read.js +5 -0
- package/dist/core/runtime/persona.js +3 -3
- package/dist/core/runtime/prospective-inventory-cli.d.ts +1 -0
- package/dist/core/runtime/prospective-inventory-cli.js +61 -0
- package/dist/core/runtime/prospective-inventory.d.ts +10 -0
- package/dist/core/runtime/prospective-inventory.js +88 -0
- package/dist/core/runtime/spawn.d.ts +3 -1
- package/dist/core/runtime/spawn.js +5 -3
- package/dist/core/scope.d.ts +26 -1
- package/dist/core/scope.js +52 -12
- package/dist/core/substrate/on-read.d.ts +7 -1
- package/dist/core/substrate/on-read.js +30 -32
- package/dist/core/substrate/render-node.d.ts +3 -2
- package/dist/core/substrate/render-node.js +3 -2
- package/dist/core/substrate/render.js +65 -24
- package/dist/core/substrate/schema.d.ts +16 -2
- package/dist/core/substrate/schema.js +14 -5
- package/dist/core/user-settings.d.ts +4 -0
- package/dist/core/user-settings.js +1 -0
- package/dist/daemon/api/__tests__/profile-launch-gates.test.js +56 -7
- package/dist/daemon/api/handlers/chat-inventory.d.ts +2 -0
- package/dist/daemon/api/handlers/chat-inventory.js +25 -0
- package/dist/daemon/api/handlers/human-requests.d.ts +2 -0
- package/dist/daemon/api/handlers/human-requests.js +409 -0
- package/dist/daemon/api/handlers/human.js +3 -0
- package/dist/daemon/api/handlers/inbox.js +3 -0
- package/dist/daemon/api/handlers/nodes.d.ts +1 -3
- package/dist/daemon/api/handlers/nodes.js +11 -46
- package/dist/daemon/api/handlers/profiles.js +7 -1
- package/dist/daemon/api/handlers/prospective-chat-inventory.d.ts +2 -0
- package/dist/daemon/api/handlers/prospective-chat-inventory.js +59 -0
- package/dist/daemon/api/handlers/reviews.js +10 -2
- package/dist/daemon/api/map.d.ts +2 -1
- package/dist/daemon/api/map.js +3 -2
- package/dist/daemon/api/server.js +6 -0
- package/dist/daemon/crtrd.js +6 -0
- package/dist/daemon/human/deliver-action.d.ts +16 -0
- package/dist/daemon/human/deliver-action.js +168 -0
- package/dist/daemon/human/finish.d.ts +8 -5
- package/dist/daemon/human/finish.js +45 -6
- package/dist/daemon/human/sweep.js +4 -1
- package/dist/daemon/reconcilers/human-delivery-lane.d.ts +10 -0
- package/dist/daemon/reconcilers/human-delivery-lane.js +41 -0
- package/dist/daemon/review/finish.d.ts +8 -3
- package/dist/daemon/review/finish.js +19 -1
- package/dist/hook-authoring.d.ts +75 -0
- package/dist/hook-authoring.js +358 -0
- package/dist/hook-process.d.ts +7 -0
- package/dist/hook-process.js +34 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/migrations/002-profile-project-memory.d.ts +2 -0
- package/dist/migrations/002-profile-project-memory.js +71 -0
- package/dist/migrations/profile-manifests.d.ts +30 -0
- package/dist/migrations/profile-manifests.js +70 -0
- package/dist/migrations/registry.js +10 -5
- package/dist/migrations/types.d.ts +28 -1
- package/dist/migrations/types.js +15 -9
- package/dist/pi-extensions/__tests__/canvas-structured-output.test.js +21 -4
- package/dist/pi-extensions/canvas-structured-output.js +85 -2
- package/dist/types.d.ts +23 -6
- package/dist/types.js +1 -0
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
- package/dist/builtin-memory/00-runtime-base.md +0 -55
- package/dist/builtin-memory/design.md +0 -55
- package/dist/clients/attach/__tests__/file-review-focus.test.js +0 -49
- /package/dist/builtin-memory/{02-lifecycle/00-terminal.md → 02-turn-lifecycle/01-terminal.md} +0 -0
- /package/dist/{clients/attach/__tests__/file-review-focus.test.d.ts → core/__tests__/fixtures/memory-slash-live-probe.d.ts} +0 -0
|
@@ -26,9 +26,10 @@ import { tmpdir } from 'node:os';
|
|
|
26
26
|
import { join, dirname } from 'node:path';
|
|
27
27
|
import { fileURLToPath } from 'node:url';
|
|
28
28
|
import { createAgentSessionServices, createAgentSessionFromServices, SessionManager, VERSION, } from '../../runtime/broker-sdk.js';
|
|
29
|
-
import { buildBrokerSession, makeBrokerUiContext } from '../../runtime/broker.js';
|
|
30
|
-
import { isLeadingEngineCommand, resolveSteerCall, chooseGuidanceDeliveryMode, promptWithNextTurnGuidance, isHiddenJsonlBranchEntry, stripHiddenEntriesFromJsonlExport, engineLeadingCommandTokens, } from '../../runtime/broker/frame-dispatch.js';
|
|
29
|
+
import { buildBrokerSession, makeBrokerUiContext, snapshotMessages } from '../../runtime/broker.js';
|
|
30
|
+
import { isLeadingEngineCommand, resolveSteerCall, chooseGuidanceDeliveryMode, promptWithNextTurnGuidance, promptWithAdvertisedCommandInvocation, isHiddenJsonlBranchEntry, stripHiddenEntriesFromJsonlExport, engineLeadingCommandTokens, } from '../../runtime/broker/frame-dispatch.js';
|
|
31
31
|
import { CANVAS_EXTENSIONS } from '../../runtime/launch.js';
|
|
32
|
+
import { prepareAdvertisedCommandPersistence } from '../../runtime/advertised-command-invocation.js';
|
|
32
33
|
// The REAL engine, assembled from broker-sdk's static SDK re-exports — bypasses
|
|
33
34
|
// the CRTR_BROKER_ENGINE seam entirely so these tests can never accidentally hit
|
|
34
35
|
// the fake (the whole point: prove the wiring the mock hid).
|
|
@@ -403,6 +404,105 @@ test('C5 — isLeadingEngineCommand against a REAL session (registered extension
|
|
|
403
404
|
rmSync(cwd, { recursive: true, force: true });
|
|
404
405
|
}
|
|
405
406
|
});
|
|
407
|
+
test('an advertised custom command persists its exact raw invocation without replacing expanded agent input', async () => {
|
|
408
|
+
const cwd = mkdtempSync(join(tmpdir(), 'crtr-advertised-command-'));
|
|
409
|
+
try {
|
|
410
|
+
const { session } = await buildBrokerSession(realEngine, cfg(cwd, { extensionPaths: [C5_EXT] }));
|
|
411
|
+
try {
|
|
412
|
+
const raw = '/gatewaycmd exact spacing ';
|
|
413
|
+
await promptWithAdvertisedCommandInvocation(session, raw, () => session.prompt(raw, {}));
|
|
414
|
+
const custom = session.sessionManager
|
|
415
|
+
.buildSessionContext()
|
|
416
|
+
.messages.find((message) => message.role === 'custom' && message.customType === 'fixture-output');
|
|
417
|
+
assert.deepEqual(custom, {
|
|
418
|
+
role: 'custom',
|
|
419
|
+
customType: 'fixture-output',
|
|
420
|
+
content: 'expanded agent input',
|
|
421
|
+
display: true,
|
|
422
|
+
details: { fixture: true },
|
|
423
|
+
crouterAdvertisedCommandInvocation: { raw },
|
|
424
|
+
timestamp: custom?.timestamp,
|
|
425
|
+
});
|
|
426
|
+
const welcome = snapshotMessages(session);
|
|
427
|
+
assert.ok(welcome);
|
|
428
|
+
const welcomeCustom = welcome.filter(({ message }) => message.role === 'custom' && message.customType === 'fixture-output');
|
|
429
|
+
assert.equal(welcomeCustom.length, 1, 'welcome reconstructs one advertised custom message');
|
|
430
|
+
assert.deepEqual(welcomeCustom[0].message
|
|
431
|
+
.crouterAdvertisedCommandInvocation, { raw }, 'welcome preserves the exact advertised invocation spacing');
|
|
432
|
+
const persisted = session.sessionManager
|
|
433
|
+
.getEntries()
|
|
434
|
+
.find((entry) => entry.type === 'custom_message' &&
|
|
435
|
+
entry.customType === 'fixture-output');
|
|
436
|
+
assert.equal(persisted?.type, 'custom_message');
|
|
437
|
+
if (persisted?.type !== 'custom_message')
|
|
438
|
+
assert.fail('custom message was not persisted');
|
|
439
|
+
assert.equal(persisted.content, 'expanded agent input');
|
|
440
|
+
assert.deepEqual(persisted.details, {
|
|
441
|
+
crouterPersistenceEnvelope: true,
|
|
442
|
+
crouterAdvertisedCommandInvocation: { raw },
|
|
443
|
+
extensionDetails: { fixture: true },
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
finally {
|
|
447
|
+
session.dispose();
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
finally {
|
|
451
|
+
rmSync(cwd, { recursive: true, force: true });
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
test('advertised invocation metadata survives nextTurn delivery and null content normalization', async () => {
|
|
455
|
+
const cwd = mkdtempSync(join(tmpdir(), 'crtr-advertised-next-turn-'));
|
|
456
|
+
try {
|
|
457
|
+
const { session } = await buildBrokerSession(realEngine, cfg(cwd, { extensionPaths: [C5_EXT] }));
|
|
458
|
+
try {
|
|
459
|
+
const rawA = '/gatewaynext first exact text ';
|
|
460
|
+
const rawB = '/gatewaynext second exact text ';
|
|
461
|
+
for (const raw of [rawA, rawB]) {
|
|
462
|
+
await promptWithAdvertisedCommandInvocation(session, raw, () => session.prompt(raw, {}));
|
|
463
|
+
}
|
|
464
|
+
const pending = session._pendingNextTurnMessages;
|
|
465
|
+
assert.deepEqual(pending.map((queued) => ({
|
|
466
|
+
content: queued.content,
|
|
467
|
+
details: queued.details,
|
|
468
|
+
invocation: queued.crouterAdvertisedCommandInvocation,
|
|
469
|
+
})), [
|
|
470
|
+
{
|
|
471
|
+
content: [],
|
|
472
|
+
details: 'extension-details',
|
|
473
|
+
invocation: { raw: rawA },
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
content: [],
|
|
477
|
+
details: 'extension-details',
|
|
478
|
+
invocation: { raw: rawB },
|
|
479
|
+
},
|
|
480
|
+
]);
|
|
481
|
+
// Simulate a queue policy draining B before A. Payload equality cannot
|
|
482
|
+
// identify them; the app-message metadata must bind each persistence.
|
|
483
|
+
for (const queued of [pending[1], pending[0]]) {
|
|
484
|
+
prepareAdvertisedCommandPersistence(session, queued);
|
|
485
|
+
session.sessionManager.appendCustomMessageEntry(queued.customType, queued.content, queued.display, queued.details);
|
|
486
|
+
}
|
|
487
|
+
const restored = session.sessionManager
|
|
488
|
+
.buildSessionContext()
|
|
489
|
+
.messages.slice(-2);
|
|
490
|
+
assert.deepEqual(restored.map((message) => ({
|
|
491
|
+
details: message.details,
|
|
492
|
+
invocation: message.crouterAdvertisedCommandInvocation,
|
|
493
|
+
})), [
|
|
494
|
+
{ details: 'extension-details', invocation: { raw: rawB } },
|
|
495
|
+
{ details: 'extension-details', invocation: { raw: rawA } },
|
|
496
|
+
]);
|
|
497
|
+
}
|
|
498
|
+
finally {
|
|
499
|
+
session.dispose();
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
finally {
|
|
503
|
+
rmSync(cwd, { recursive: true, force: true });
|
|
504
|
+
}
|
|
505
|
+
});
|
|
406
506
|
test('C5 — chooseGuidanceDeliveryMode truth table (pure)', () => {
|
|
407
507
|
// idle prompt (routeCall='prompt', not streaming) -> guidance precedes the
|
|
408
508
|
// human call, in the SAME turn.
|
package/dist/core/bootstrap.js
CHANGED
|
@@ -14,6 +14,12 @@ function shouldSkipForArgv(argv) {
|
|
|
14
14
|
const sub = argv[2];
|
|
15
15
|
if (sub === undefined)
|
|
16
16
|
return true;
|
|
17
|
+
// `sys migrate` converges state that every other read already depends on:
|
|
18
|
+
// resolving a scope here would read the selected profile through the current
|
|
19
|
+
// reader (blind to a pre-migration manifest) and could scaffold a cwd scope
|
|
20
|
+
// the migration then has to treat as real. It owns its own discovery.
|
|
21
|
+
if (sub === 'sys' && argv[3] === 'migrate')
|
|
22
|
+
return true;
|
|
17
23
|
return SKIP_SUBCOMMANDS.has(sub);
|
|
18
24
|
}
|
|
19
25
|
export function ensureProjectScope(argv) {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { execFileSync } from 'node:child_process';
|
|
11
11
|
import { resolve } from 'node:path';
|
|
12
12
|
import { dashboardRowsAllFromSource, enrichRowsFromSource, loadPreviewFromSource, renderForestFromSource } from '../render-source.js';
|
|
13
|
+
import { fullName } from '../labels.js';
|
|
13
14
|
import { setupTerminal, restoreTerminal, getTerminalSize, parseKeypress, } from '../../tui/terminal.js';
|
|
14
15
|
import { buildTree, flatten, pruneNode, TABS } from './model.js';
|
|
15
16
|
import { loadPins, savePins } from './pins.js';
|
|
@@ -483,8 +484,10 @@ export async function runBrowse(opts) {
|
|
|
483
484
|
/* best-effort — a rename failure never wedges the picker */
|
|
484
485
|
}
|
|
485
486
|
const n = tree.nodes.get(id);
|
|
486
|
-
if (n !== undefined)
|
|
487
|
-
n.row.
|
|
487
|
+
if (n !== undefined) {
|
|
488
|
+
n.row.handle = name;
|
|
489
|
+
n.row.name = fullName({ name, kind: n.row.kind, description: n.row.description });
|
|
490
|
+
}
|
|
488
491
|
}
|
|
489
492
|
recompute(state.renameTarget ?? undefined);
|
|
490
493
|
scheduleFlush();
|
|
@@ -70,11 +70,8 @@ export interface SnippetLine {
|
|
|
70
70
|
text: string;
|
|
71
71
|
hi: Set<number>;
|
|
72
72
|
}
|
|
73
|
-
/**
|
|
74
|
-
*
|
|
75
|
-
* scattered subsequence indices, else empty (empty query). This is a different,
|
|
76
|
-
* stricter model than the subsequence super-search on purpose — a contiguous span
|
|
77
|
-
* is what reads as a highlight. */
|
|
73
|
+
/** The literal case-insensitive substring span in a conversation preview.
|
|
74
|
+
* Scattered letters are not a message match and never receive a highlight. */
|
|
78
75
|
export declare function highlightIndices(query: string, text: string): Set<number>;
|
|
79
76
|
/**
|
|
80
77
|
* Build the preview snippet for `text` under the live `query`: up to `maxLines`
|
|
@@ -91,17 +88,27 @@ export declare function previewSnippet(query: string, text: string, width: numbe
|
|
|
91
88
|
* windowed in the preview, so search matches a prompt from ANYWHERE in the
|
|
92
89
|
* conversation, not just the first one. */
|
|
93
90
|
export declare function promptText(row: DashboardRow): string;
|
|
94
|
-
/** Does this row match the live query?
|
|
95
|
-
*
|
|
96
|
-
* in the conversation (`promptText`). Empty query matches everything. */
|
|
91
|
+
/** Does this row match the live query? Empty query matches everything; a
|
|
92
|
+
* non-empty query uses the same fields and match tiers as relevance ranking. */
|
|
97
93
|
export declare function queryMatch(query: string, row: DashboardRow): boolean;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
|
|
104
|
-
|
|
94
|
+
export type MatchTier = 0 | 1 | 2 | 3;
|
|
95
|
+
export interface FieldMatch {
|
|
96
|
+
/** 3 = bounded word/phrase, 2 = contiguous partial, 1 = subsequence. */
|
|
97
|
+
tier: MatchTier;
|
|
98
|
+
/** Tie-break quality within one tier. */
|
|
99
|
+
quality: number;
|
|
100
|
+
}
|
|
101
|
+
export interface RelevanceScore {
|
|
102
|
+
tier: MatchTier;
|
|
103
|
+
strength: number;
|
|
104
|
+
}
|
|
105
|
+
/** One field's best match. Match class is deliberately separate from quality:
|
|
106
|
+
* a bounded literal always outranks a contiguous partial, which always outranks
|
|
107
|
+
* a scattered subsequence, regardless of field weights. */
|
|
108
|
+
export declare function fieldMatch(query: string, text: string): FieldMatch;
|
|
109
|
+
/** Rank a row by its strongest match class, then by weighted evidence only from
|
|
110
|
+
* that class. Weaker classes cannot add up to beat one stronger literal match. */
|
|
111
|
+
export declare function scoreRow(query: string, row: DashboardRow): RelevanceScore;
|
|
105
112
|
/** The "most recent message" signal for the attention sort: the pi session-file
|
|
106
113
|
* mtime (ms) the snapshot stamps on each row, falling back to the `created`
|
|
107
114
|
* birth timestamp when no session file exists. Larger = more recent.
|
|
@@ -111,6 +118,9 @@ export declare function scoreRow(query: string, row: DashboardRow): number;
|
|
|
111
118
|
* update this single accessor. `created` is ISO 8601, so its epoch-ms is a
|
|
112
119
|
* monotonic stand-in within the same tier. */
|
|
113
120
|
export declare function attentionMtime(row: DashboardRow): number;
|
|
121
|
+
/** Up to a 10% relevance lift for recent conversation activity, halving after a
|
|
122
|
+
* week. Match tier remains separate, so freshness never promotes fuzzy over literal. */
|
|
123
|
+
export declare function relevanceFreshness(row: DashboardRow, now: number): number;
|
|
114
124
|
/** Attention tier (lower = higher priority, shown first):
|
|
115
125
|
* T0 — attached AND streaming (`viewed && streaming`)
|
|
116
126
|
* T1 — attached, not streaming (`viewed`)
|
|
@@ -160,24 +160,18 @@ export function matchIndices(query, text) {
|
|
|
160
160
|
}
|
|
161
161
|
/** Chars of leading context kept before the match start when windowing. */
|
|
162
162
|
const SNIPPET_LEAD = 12;
|
|
163
|
-
/**
|
|
164
|
-
*
|
|
165
|
-
* scattered subsequence indices, else empty (empty query). This is a different,
|
|
166
|
-
* stricter model than the subsequence super-search on purpose — a contiguous span
|
|
167
|
-
* is what reads as a highlight. */
|
|
163
|
+
/** The literal case-insensitive substring span in a conversation preview.
|
|
164
|
+
* Scattered letters are not a message match and never receive a highlight. */
|
|
168
165
|
export function highlightIndices(query, text) {
|
|
169
166
|
if (query === '')
|
|
170
167
|
return new Set();
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
return out;
|
|
179
|
-
}
|
|
180
|
-
return matchIndices(query, text);
|
|
168
|
+
const sub = text.toLowerCase().indexOf(query.toLowerCase());
|
|
169
|
+
if (sub < 0)
|
|
170
|
+
return new Set();
|
|
171
|
+
const out = new Set();
|
|
172
|
+
for (let i = sub; i < sub + query.length; i++)
|
|
173
|
+
out.add(i);
|
|
174
|
+
return out;
|
|
181
175
|
}
|
|
182
176
|
/** Greedy word-wrap of single-spaced `s` (caller normalizes whitespace) to `width`
|
|
183
177
|
* cols × `maxLines` lines, returning each line's text AND its start offset in `s`.
|
|
@@ -233,17 +227,7 @@ export function previewSnippet(query, text, width, maxLines) {
|
|
|
233
227
|
return [];
|
|
234
228
|
// 1. Locate the match in the normalized text + choose a window start that keeps
|
|
235
229
|
// a little leading context, snapped to a word boundary.
|
|
236
|
-
|
|
237
|
-
if (query !== '') {
|
|
238
|
-
const sub = norm.toLowerCase().indexOf(query.toLowerCase());
|
|
239
|
-
if (sub >= 0)
|
|
240
|
-
matchStart = sub;
|
|
241
|
-
else {
|
|
242
|
-
const ix = matchIndices(query, norm);
|
|
243
|
-
if (ix.size > 0)
|
|
244
|
-
matchStart = Math.min(...ix);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
230
|
+
const matchStart = query === '' ? -1 : norm.toLowerCase().indexOf(query.toLowerCase());
|
|
247
231
|
let windowStart = 0;
|
|
248
232
|
if (matchStart > SNIPPET_LEAD) {
|
|
249
233
|
let s = matchStart - SNIPPET_LEAD;
|
|
@@ -274,52 +258,69 @@ function shortId(id) {
|
|
|
274
258
|
export function promptText(row) {
|
|
275
259
|
return row.prompts ?? row.goal ?? '';
|
|
276
260
|
}
|
|
277
|
-
/** Does this row match the live query?
|
|
278
|
-
*
|
|
279
|
-
* in the conversation (`promptText`). Empty query matches everything. */
|
|
261
|
+
/** Does this row match the live query? Empty query matches everything; a
|
|
262
|
+
* non-empty query uses the same fields and match tiers as relevance ranking. */
|
|
280
263
|
export function queryMatch(query, row) {
|
|
281
|
-
|
|
282
|
-
return true;
|
|
283
|
-
const prompt = promptText(row);
|
|
284
|
-
return (fuzzyMatch(query, row.name) ||
|
|
285
|
-
fuzzyMatch(query, row.kind) ||
|
|
286
|
-
fuzzyMatch(query, shortId(row.node_id)) ||
|
|
287
|
-
(prompt !== '' && fuzzyMatch(query, prompt)));
|
|
264
|
+
return query === '' || scoreRow(query, row).tier > 0;
|
|
288
265
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
266
|
+
const NO_MATCH = { tier: 0, quality: 0 };
|
|
267
|
+
const WORD_CHAR = /[\p{L}\p{N}]/u;
|
|
268
|
+
function isWordChar(char) {
|
|
269
|
+
return char !== undefined && WORD_CHAR.test(char);
|
|
270
|
+
}
|
|
271
|
+
/** One field's best match. Match class is deliberately separate from quality:
|
|
272
|
+
* a bounded literal always outranks a contiguous partial, which always outranks
|
|
273
|
+
* a scattered subsequence, regardless of field weights. */
|
|
274
|
+
export function fieldMatch(query, text) {
|
|
296
275
|
if (query === '' || text === '')
|
|
297
|
-
return
|
|
276
|
+
return NO_MATCH;
|
|
298
277
|
const q = query.toLowerCase();
|
|
299
278
|
const t = text.toLowerCase();
|
|
300
|
-
const
|
|
301
|
-
if (
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
279
|
+
const firstIdx = t.indexOf(q);
|
|
280
|
+
if (firstIdx >= 0) {
|
|
281
|
+
for (let idx = firstIdx; idx >= 0; idx = t.indexOf(q, idx + 1)) {
|
|
282
|
+
const end = idx + q.length;
|
|
283
|
+
const leftBounded = !isWordChar(q[0]) || !isWordChar(t[idx - 1]);
|
|
284
|
+
const rightBounded = !isWordChar(q[q.length - 1]) || !isWordChar(t[end]);
|
|
285
|
+
if (leftBounded && rightBounded)
|
|
286
|
+
return { tier: 3, quality: t === q ? 1 : 0.95 };
|
|
287
|
+
}
|
|
288
|
+
const position = Math.min(0.2, firstIdx / 400);
|
|
289
|
+
return { tier: 2, quality: (firstIdx === 0 ? 1 : 0.8) - position };
|
|
307
290
|
}
|
|
308
|
-
|
|
291
|
+
const indices = [...matchIndices(q, t)];
|
|
292
|
+
if (indices.length === 0)
|
|
293
|
+
return NO_MATCH;
|
|
294
|
+
const span = indices[indices.length - 1] - indices[0] + 1;
|
|
295
|
+
return { tier: 1, quality: q.length / span };
|
|
296
|
+
}
|
|
297
|
+
/** Explicit handles and user prompts are primary. The generated description is
|
|
298
|
+
* searchable but lower-weight; assistant replies are preview-only. */
|
|
299
|
+
const FIELD_WEIGHTS = { handle: 4, prompt: 4, kind: 2, description: 1.5, id: 1 };
|
|
300
|
+
function explicitHandle(row) {
|
|
301
|
+
const handle = row.handle ?? row.name;
|
|
302
|
+
return handle === row.kind ? '' : handle;
|
|
309
303
|
}
|
|
310
|
-
/**
|
|
311
|
-
*
|
|
312
|
-
const FIELD_WEIGHTS = { name: 4, kind: 2, id: 1, prompt: 1.5 };
|
|
313
|
-
/** Weighted relevance of a row to the query across all searched fields. 0 means
|
|
314
|
-
* no field matched (excluded from relevance results, same as `queryMatch`). */
|
|
304
|
+
/** Rank a row by its strongest match class, then by weighted evidence only from
|
|
305
|
+
* that class. Weaker classes cannot add up to beat one stronger literal match. */
|
|
315
306
|
export function scoreRow(query, row) {
|
|
316
307
|
if (query === '')
|
|
317
|
-
return 0;
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
308
|
+
return { tier: 0, strength: 0 };
|
|
309
|
+
const fields = [
|
|
310
|
+
{ text: explicitHandle(row), weight: FIELD_WEIGHTS.handle, minimumTier: 1 },
|
|
311
|
+
// Scattered subsequences in long messages are random noise, not matches.
|
|
312
|
+
{ text: promptText(row), weight: FIELD_WEIGHTS.prompt, minimumTier: 2 },
|
|
313
|
+
{ text: row.kind, weight: FIELD_WEIGHTS.kind, minimumTier: 1 },
|
|
314
|
+
{ text: row.description ?? '', weight: FIELD_WEIGHTS.description, minimumTier: 1 },
|
|
315
|
+
{ text: shortId(row.node_id), weight: FIELD_WEIGHTS.id, minimumTier: 1 },
|
|
316
|
+
];
|
|
317
|
+
const matches = fields.map(({ text, weight, minimumTier }) => {
|
|
318
|
+
const match = fieldMatch(query, text);
|
|
319
|
+
return { match: match.tier >= minimumTier ? match : NO_MATCH, weight };
|
|
320
|
+
});
|
|
321
|
+
const tier = Math.max(...matches.map(({ match }) => match.tier));
|
|
322
|
+
const strength = matches.reduce((sum, { match, weight }) => sum + (match.tier === tier ? match.quality * weight : 0), 0);
|
|
323
|
+
return { tier, strength };
|
|
323
324
|
}
|
|
324
325
|
// ---------------------------------------------------------------------------
|
|
325
326
|
// Attention tiering (default sort)
|
|
@@ -339,6 +340,14 @@ export function attentionMtime(row) {
|
|
|
339
340
|
const t = Date.parse(row.created);
|
|
340
341
|
return Number.isFinite(t) ? t : 0;
|
|
341
342
|
}
|
|
343
|
+
const RELEVANCE_RECENCY_MAX_BOOST = 0.1;
|
|
344
|
+
const RELEVANCE_RECENCY_HALF_LIFE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
345
|
+
/** Up to a 10% relevance lift for recent conversation activity, halving after a
|
|
346
|
+
* week. Match tier remains separate, so freshness never promotes fuzzy over literal. */
|
|
347
|
+
export function relevanceFreshness(row, now) {
|
|
348
|
+
const age = Math.max(0, now - attentionMtime(row));
|
|
349
|
+
return 1 + RELEVANCE_RECENCY_MAX_BOOST / (1 + age / RELEVANCE_RECENCY_HALF_LIFE_MS);
|
|
350
|
+
}
|
|
342
351
|
/** Attention tier (lower = higher priority, shown first):
|
|
343
352
|
* T0 — attached AND streaming (`viewed && streaming`)
|
|
344
353
|
* T1 — attached, not streaming (`viewed`)
|
|
@@ -427,9 +436,21 @@ export function flatten(tree, opts) {
|
|
|
427
436
|
}
|
|
428
437
|
else {
|
|
429
438
|
const score = new Map();
|
|
439
|
+
const rowOf = (id) => tree.nodes.get(id).row;
|
|
440
|
+
const now = Date.now();
|
|
430
441
|
for (const id of ids)
|
|
431
|
-
score.set(id, scoreRow(query,
|
|
432
|
-
ids.sort((a, b) =>
|
|
442
|
+
score.set(id, scoreRow(query, rowOf(id)));
|
|
443
|
+
ids.sort((a, b) => {
|
|
444
|
+
const aScore = score.get(a);
|
|
445
|
+
const bScore = score.get(b);
|
|
446
|
+
const aStrength = aScore.strength * relevanceFreshness(rowOf(a), now);
|
|
447
|
+
const bStrength = bScore.strength * relevanceFreshness(rowOf(b), now);
|
|
448
|
+
return (pinRank(a) - pinRank(b))
|
|
449
|
+
|| (bScore.tier - aScore.tier)
|
|
450
|
+
|| (bStrength - aStrength)
|
|
451
|
+
|| (attentionMtime(rowOf(b)) - attentionMtime(rowOf(a)))
|
|
452
|
+
|| byRecency(a, b);
|
|
453
|
+
});
|
|
433
454
|
}
|
|
434
455
|
return ids.map((id) => ({ id, depth: 0, hasChildren: false, collapsed: false, matched: true }));
|
|
435
456
|
}
|
package/dist/core/canvas/db.js
CHANGED
|
@@ -1153,6 +1153,28 @@ ALTER TABLE crons ADD COLUMN held INTEGER NOT NULL DEFAULT 0;
|
|
|
1153
1153
|
ALTER TABLE crons ADD COLUMN last_poke_at TEXT;
|
|
1154
1154
|
`);
|
|
1155
1155
|
}
|
|
1156
|
+
/** v33 — durable, daemon-owned attempts to deliver settled human completions. */
|
|
1157
|
+
function addHumanActionDeliveries(db) {
|
|
1158
|
+
db.exec(`
|
|
1159
|
+
CREATE TABLE human_action_deliveries (
|
|
1160
|
+
request_id TEXT PRIMARY KEY,
|
|
1161
|
+
state TEXT NOT NULL,
|
|
1162
|
+
attempt INTEGER NOT NULL DEFAULT 0,
|
|
1163
|
+
argv_json TEXT NOT NULL,
|
|
1164
|
+
cwd TEXT NOT NULL,
|
|
1165
|
+
completion_json TEXT NOT NULL,
|
|
1166
|
+
next_attempt_at INTEGER NOT NULL,
|
|
1167
|
+
claimed_at INTEGER,
|
|
1168
|
+
claim_owner TEXT,
|
|
1169
|
+
accepted_at INTEGER,
|
|
1170
|
+
permanent_failed_at INTEGER,
|
|
1171
|
+
last_failure_json TEXT,
|
|
1172
|
+
created_at INTEGER NOT NULL,
|
|
1173
|
+
updated_at INTEGER NOT NULL
|
|
1174
|
+
);
|
|
1175
|
+
CREATE INDEX idx_human_action_deliveries_due ON human_action_deliveries(state, next_attempt_at);
|
|
1176
|
+
`);
|
|
1177
|
+
}
|
|
1156
1178
|
/** The journaled lane of the state-migration registry, adapted to this chain's
|
|
1157
1179
|
* step shape. Registry order is preserved, so these versions stay stable as
|
|
1158
1180
|
* long as the registry is append-only (same contract as the local list). */
|
|
@@ -1198,6 +1220,7 @@ export const MIGRATIONS = [
|
|
|
1198
1220
|
/* v30 */ dropConsultOutbox,
|
|
1199
1221
|
/* v31 */ addReviewSubmitRequested,
|
|
1200
1222
|
/* v32 */ addCronHeldColumns,
|
|
1223
|
+
/* v33 */ addHumanActionDeliveries,
|
|
1201
1224
|
...JOURNALED_STATE_MIGRATIONS,
|
|
1202
1225
|
];
|
|
1203
1226
|
/** Migration indexes that manage their OWN transaction and therefore must not
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export type ActionDeliveryState = 'pending' | 'running' | 'accepted' | 'permanent_failed';
|
|
2
|
+
export interface ActionDeliveryFailure {
|
|
3
|
+
kind: 'exit' | 'signal' | 'timeout' | 'spawn_error';
|
|
4
|
+
exitCode?: number;
|
|
5
|
+
signal?: string;
|
|
6
|
+
message?: string;
|
|
7
|
+
stderr?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ActionDeliveryRecord {
|
|
10
|
+
requestId: string;
|
|
11
|
+
state: ActionDeliveryState;
|
|
12
|
+
attempt: number;
|
|
13
|
+
argv: string[];
|
|
14
|
+
cwd: string;
|
|
15
|
+
completion: unknown;
|
|
16
|
+
nextAttemptAt: number;
|
|
17
|
+
claimedAt: number | null;
|
|
18
|
+
claimOwner: string | null;
|
|
19
|
+
acceptedAt: number | null;
|
|
20
|
+
permanentFailedAt: number | null;
|
|
21
|
+
lastFailure: ActionDeliveryFailure | null;
|
|
22
|
+
createdAt: number;
|
|
23
|
+
updatedAt: number;
|
|
24
|
+
}
|
|
25
|
+
/** Insert the one delivery row for a settled action-bound request. Idempotent
|
|
26
|
+
* on requestId: a replayed settlement must not create a second row. */
|
|
27
|
+
export declare function enqueueActionDelivery(row: {
|
|
28
|
+
requestId: string;
|
|
29
|
+
argv: string[];
|
|
30
|
+
cwd: string;
|
|
31
|
+
/** Complete HumanCompletionV1 with delivery.attempt set to 0. */
|
|
32
|
+
completion: unknown;
|
|
33
|
+
}): void;
|
|
34
|
+
/** Delivery state for `GET /v1/human/requests/:id`; null when no row exists. */
|
|
35
|
+
export declare function readActionDelivery(requestId: string): ActionDeliveryRecord | null;
|
|
36
|
+
/** Due pending records in deterministic order for the daemon's serial tick. */
|
|
37
|
+
export declare function dueActionDeliveries(now: number): ActionDeliveryRecord[];
|
|
38
|
+
/** Atomically start one due delivery attempt. A null result lost the claim. */
|
|
39
|
+
export declare function claimActionDelivery(requestId: string, now: number, claimOwner: string): ActionDeliveryRecord | null;
|
|
40
|
+
export type ActionDeliverySettlement = {
|
|
41
|
+
state: 'accepted';
|
|
42
|
+
} | {
|
|
43
|
+
state: 'permanent_failed';
|
|
44
|
+
failure: ActionDeliveryFailure;
|
|
45
|
+
} | {
|
|
46
|
+
state: 'pending';
|
|
47
|
+
nextAttemptAt: number;
|
|
48
|
+
failure: ActionDeliveryFailure;
|
|
49
|
+
};
|
|
50
|
+
/** Settle only the still-owned running attempt, so a stale process cannot overwrite a newer claim. */
|
|
51
|
+
export declare function settleActionDelivery(requestId: string, attempt: number, claimOwner: string, settlement: ActionDeliverySettlement, now: number): boolean;
|
|
52
|
+
/** A new daemon deliberately redelivers interrupted completions at least once. */
|
|
53
|
+
export declare function recoverRunningActionDeliveries(now: number): number;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Durable action-completion delivery state. The terminal ticket is composed
|
|
2
|
+
// before this row exists; this module schedules its already-complete document.
|
|
3
|
+
import { openDb, withCanvasWrite } from './db.js';
|
|
4
|
+
function actionDeliveryFrom(row) {
|
|
5
|
+
return {
|
|
6
|
+
requestId: row.request_id,
|
|
7
|
+
state: row.state,
|
|
8
|
+
attempt: row.attempt,
|
|
9
|
+
argv: JSON.parse(row.argv_json),
|
|
10
|
+
cwd: row.cwd,
|
|
11
|
+
completion: JSON.parse(row.completion_json),
|
|
12
|
+
nextAttemptAt: row.next_attempt_at,
|
|
13
|
+
claimedAt: row.claimed_at,
|
|
14
|
+
claimOwner: row.claim_owner,
|
|
15
|
+
acceptedAt: row.accepted_at,
|
|
16
|
+
permanentFailedAt: row.permanent_failed_at,
|
|
17
|
+
lastFailure: row.last_failure_json === null ? null : JSON.parse(row.last_failure_json),
|
|
18
|
+
createdAt: row.created_at,
|
|
19
|
+
updatedAt: row.updated_at,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Insert the one delivery row for a settled action-bound request. Idempotent
|
|
23
|
+
* on requestId: a replayed settlement must not create a second row. */
|
|
24
|
+
export function enqueueActionDelivery(row) {
|
|
25
|
+
const now = Date.now();
|
|
26
|
+
withCanvasWrite((db) => {
|
|
27
|
+
db.prepare(`INSERT INTO human_action_deliveries
|
|
28
|
+
(request_id, state, attempt, argv_json, cwd, completion_json, next_attempt_at, created_at, updated_at)
|
|
29
|
+
VALUES (?, 'pending', 0, ?, ?, ?, ?, ?, ?)
|
|
30
|
+
ON CONFLICT(request_id) DO NOTHING`).run(row.requestId, JSON.stringify(row.argv), row.cwd, JSON.stringify(row.completion), now, now, now);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/** Delivery state for `GET /v1/human/requests/:id`; null when no row exists. */
|
|
34
|
+
export function readActionDelivery(requestId) {
|
|
35
|
+
const row = openDb()
|
|
36
|
+
.prepare('SELECT * FROM human_action_deliveries WHERE request_id = ?')
|
|
37
|
+
.get(requestId);
|
|
38
|
+
return row === undefined ? null : actionDeliveryFrom(row);
|
|
39
|
+
}
|
|
40
|
+
/** Due pending records in deterministic order for the daemon's serial tick. */
|
|
41
|
+
export function dueActionDeliveries(now) {
|
|
42
|
+
return openDb()
|
|
43
|
+
.prepare(`SELECT * FROM human_action_deliveries
|
|
44
|
+
WHERE state = 'pending' AND next_attempt_at <= ?
|
|
45
|
+
ORDER BY next_attempt_at, created_at, request_id`)
|
|
46
|
+
.all(now).map(actionDeliveryFrom);
|
|
47
|
+
}
|
|
48
|
+
/** Atomically start one due delivery attempt. A null result lost the claim. */
|
|
49
|
+
export function claimActionDelivery(requestId, now, claimOwner) {
|
|
50
|
+
return withCanvasWrite((db) => {
|
|
51
|
+
const claimed = db.prepare(`UPDATE human_action_deliveries
|
|
52
|
+
SET state = 'running', attempt = attempt + 1, claimed_at = ?, claim_owner = ?, updated_at = ?
|
|
53
|
+
WHERE request_id = ? AND state = 'pending' AND next_attempt_at <= ?`).run(now, claimOwner, now, requestId, now);
|
|
54
|
+
if (Number(claimed.changes) === 0)
|
|
55
|
+
return null;
|
|
56
|
+
const row = db.prepare('SELECT * FROM human_action_deliveries WHERE request_id = ?').get(requestId);
|
|
57
|
+
return actionDeliveryFrom(row);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/** Settle only the still-owned running attempt, so a stale process cannot overwrite a newer claim. */
|
|
61
|
+
export function settleActionDelivery(requestId, attempt, claimOwner, settlement, now) {
|
|
62
|
+
const failure = settlement.state === 'accepted' ? null : JSON.stringify(settlement.failure);
|
|
63
|
+
const result = withCanvasWrite((db) => db.prepare(`UPDATE human_action_deliveries
|
|
64
|
+
SET state = ?, next_attempt_at = ?, claimed_at = NULL, claim_owner = NULL,
|
|
65
|
+
accepted_at = ?, permanent_failed_at = ?, last_failure_json = ?, updated_at = ?
|
|
66
|
+
WHERE request_id = ? AND state = 'running' AND attempt = ? AND claim_owner = ?`).run(settlement.state, settlement.state === 'pending' ? settlement.nextAttemptAt : now, settlement.state === 'accepted' ? now : null, settlement.state === 'permanent_failed' ? now : null, failure, now, requestId, attempt, claimOwner));
|
|
67
|
+
return Number(result.changes) === 1;
|
|
68
|
+
}
|
|
69
|
+
/** A new daemon deliberately redelivers interrupted completions at least once. */
|
|
70
|
+
export function recoverRunningActionDeliveries(now) {
|
|
71
|
+
const result = withCanvasWrite((db) => db.prepare(`UPDATE human_action_deliveries
|
|
72
|
+
SET state = 'pending', next_attempt_at = ?, claimed_at = NULL, claim_owner = NULL, updated_at = ?
|
|
73
|
+
WHERE state = 'running'`).run(now, now));
|
|
74
|
+
return Number(result.changes);
|
|
75
|
+
}
|
|
@@ -16,7 +16,13 @@ export declare function renderTreeFromSource(source: CanvasSource, rootId: strin
|
|
|
16
16
|
export declare function renderForestFromSource(source: CanvasSource): Promise<string>;
|
|
17
17
|
export interface DashboardRow {
|
|
18
18
|
node_id: string;
|
|
19
|
+
/** Full display label: explicit handle plus generated description. */
|
|
19
20
|
name: string;
|
|
21
|
+
/** Explicit node handle, kept separate so search does not treat generated text
|
|
22
|
+
* as part of the higher-weight name field. */
|
|
23
|
+
handle?: string;
|
|
24
|
+
/** Generated first-task description. Searchable below user-authored prompts. */
|
|
25
|
+
description?: string;
|
|
20
26
|
status: NodeStatus;
|
|
21
27
|
kind: string;
|
|
22
28
|
mode: string;
|
|
@@ -366,6 +366,7 @@ export function dashboardRowFromNodeRow(row, focusedNodeIds, remote) {
|
|
|
366
366
|
return {
|
|
367
367
|
node_id: row.node_id,
|
|
368
368
|
name: row.name, // handle only; enrichRow upgrades to fullName (meta.description)
|
|
369
|
+
handle: row.name,
|
|
369
370
|
status: row.status,
|
|
370
371
|
kind: row.kind,
|
|
371
372
|
mode: row.mode,
|
|
@@ -399,6 +400,8 @@ export async function dashboardRowsFromSource(source, rootId) {
|
|
|
399
400
|
rows.push({
|
|
400
401
|
node_id: id,
|
|
401
402
|
name: fullName(node),
|
|
403
|
+
handle: node.name,
|
|
404
|
+
description: node.description,
|
|
402
405
|
status: node.status,
|
|
403
406
|
kind: node.kind,
|
|
404
407
|
mode: node.mode,
|
|
@@ -440,8 +443,11 @@ export async function enrichRowsFromSource(source, rows, asks) {
|
|
|
440
443
|
// the client's cold-daemon path even though the daemon is still running.
|
|
441
444
|
for (const row of todo) {
|
|
442
445
|
const meta = await source.getNode(row.node_id);
|
|
443
|
-
if (meta !== null)
|
|
446
|
+
if (meta !== null) {
|
|
444
447
|
row.name = fullName(meta);
|
|
448
|
+
row.handle = meta.name;
|
|
449
|
+
row.description = meta.description;
|
|
450
|
+
}
|
|
445
451
|
row.ctx_tokens = remote ? 0 : (readNodeTelemetry(row.node_id).tokens_in ?? 0);
|
|
446
452
|
row.asks = askMap[row.node_id] ?? 0;
|
|
447
453
|
row.enriched = true;
|