@north-light/crouter 0.3.191 → 0.3.193
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 +31 -10
- package/dist/api/client.js +83 -24
- package/dist/api/dto/bash-jobs.d.ts +22 -0
- package/dist/api/dto/bash-jobs.js +9 -0
- package/dist/api/dto/human.d.ts +3 -4
- package/dist/api/dto/inbox.d.ts +106 -65
- package/dist/api/dto/inbox.js +2 -8
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/routes.d.ts +7 -0
- package/dist/api/routes.js +7 -0
- package/dist/builtin-memory/internal/storage-tiers.md +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/serial/provider-rotation.test.ts +101 -3
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +6 -2
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +68 -14
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +87 -13
- package/dist/clients/attach/__tests__/chat-view-snapshot-ordering.test.js +2 -2
- package/dist/clients/attach/__tests__/group-activity.test.js +2 -2
- package/dist/clients/attach/chrome/canvas-panels.d.ts +1 -1
- package/dist/clients/attach/chrome/canvas-panels.js +1 -1
- package/dist/clients/attach/input/overlay-owner.js +1 -1
- package/dist/clients/attach/overlays/auth.js +34 -8
- package/dist/clients/attach/overlays/pickers.d.ts +5 -8
- package/dist/clients/attach/overlays/pickers.js +37 -43
- package/dist/clients/attach/viewer.js +643 -620
- package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +38 -128
- package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +72 -148
- package/dist/clients/inbox/controller.d.ts +2 -3
- package/dist/clients/inbox/controller.js +41 -42
- package/dist/clients/inbox/page-adapter.d.ts +33 -0
- package/dist/clients/inbox/page-adapter.js +54 -0
- package/dist/clients/inbox/resolve.d.ts +2 -2
- package/dist/clients/inbox/resolve.js +1 -1
- package/dist/clients/inbox/review-visibility.d.ts +1 -1
- package/dist/clients/inbox/review-visibility.js +1 -1
- package/dist/clients/inbox/tui/ansi.d.ts +1 -1
- package/dist/clients/inbox/tui/ansi.js +2 -2
- package/dist/clients/inbox/tui/input.d.ts +1 -3
- package/dist/clients/inbox/tui/input.js +242 -553
- package/dist/clients/inbox/tui/panel.d.ts +3 -1
- package/dist/clients/inbox/tui/panel.js +59 -187
- package/dist/clients/inbox/tui/render.d.ts +0 -25
- package/dist/clients/inbox/tui/render.js +165 -416
- package/dist/clients/inbox/tui/slots.d.ts +32 -0
- package/dist/clients/inbox/tui/slots.js +156 -0
- package/dist/clients/inbox/tui/types.d.ts +25 -13
- package/dist/clients/inbox/tui.js +5 -3
- package/dist/commands/attention.js +2 -2
- package/dist/commands/human/doc.js +66 -20
- package/dist/commands/human/prompts.d.ts +3 -3
- package/dist/commands/human/prompts.js +158 -112
- package/dist/commands/human/queue.d.ts +0 -1
- package/dist/commands/human/queue.js +34 -94
- package/dist/commands/human/shared.d.ts +10 -4
- package/dist/commands/human/shared.js +62 -13
- package/dist/commands/human.js +8 -4
- package/dist/commands/memory/read.js +13 -5
- package/dist/commands/node/bash.js +15 -28
- package/dist/commands/surface/node/focus.js +1 -1
- package/dist/core/__tests__/human-deliver.test.js +40 -46
- package/dist/core/__tests__/seam/broker-attach-stream.test.js +7 -1
- package/dist/core/__tests__/serial/human-deliver-e2e.test.js +40 -31
- package/dist/core/__tests__/serial/spawn-root.test.js +3 -1
- package/dist/core/__tests__/spawn-worktree-id-conflict.test.js +3 -1
- package/dist/core/auth-interaction.d.ts +2 -2
- package/dist/core/bash-jobs.d.ts +32 -0
- package/dist/core/bash-jobs.js +72 -1
- package/dist/core/canvas/__tests__/attention.test.js +16 -10
- package/dist/core/canvas/__tests__/render-remote.test.js +9 -3
- package/dist/core/canvas/attention.js +1 -1
- package/dist/core/canvas/browse/app.js +1 -1
- package/dist/core/canvas/nav-model.d.ts +1 -1
- package/dist/core/canvas/nav-model.js +1 -1
- package/dist/core/canvas/remote-canvas-source.js +1 -1
- package/dist/core/config.js +4 -1
- package/dist/core/human/__tests__/page-render.test.d.ts +1 -0
- package/dist/core/human/__tests__/page-render.test.js +49 -0
- package/dist/core/human/__tests__/page-tickets.test.d.ts +1 -0
- package/dist/core/human/__tests__/page-tickets.test.js +112 -0
- package/dist/core/human/__tests__/page.test.d.ts +1 -0
- package/dist/core/human/__tests__/page.test.js +139 -0
- package/dist/core/human/__tests__/serial/inbox-core.test.js +42 -32
- package/dist/core/human/claim.js +2 -2
- package/dist/core/human/convention.d.ts +4 -4
- package/dist/core/human/convention.js +6 -13
- package/dist/core/human/markdown-html.d.ts +8 -0
- package/dist/core/human/markdown-html.js +581 -0
- package/dist/core/human/page-catalog.d.ts +6 -0
- package/dist/core/human/page-catalog.js +43 -0
- package/dist/core/human/page-render.d.ts +3 -0
- package/dist/core/human/page-render.js +131 -0
- package/dist/core/human/page-schema.d.ts +312 -0
- package/dist/core/human/page-schema.js +291 -0
- package/dist/core/human/page-synth.d.ts +9 -0
- package/dist/core/human/page-synth.js +17 -0
- package/dist/core/human/page.d.ts +22 -0
- package/dist/core/human/page.js +427 -0
- package/dist/core/human/review-schema.d.ts +19 -0
- package/dist/core/human/review-schema.js +34 -0
- package/dist/core/human/scan.d.ts +12 -3
- package/dist/core/human/scan.js +69 -26
- package/dist/core/human/summary.d.ts +5 -6
- package/dist/core/human/summary.js +54 -37
- package/dist/core/human/tickets.d.ts +61 -11
- package/dist/core/human/tickets.js +100 -57
- package/dist/core/human/types.d.ts +21 -66
- package/dist/core/human/types.js +1 -5
- package/dist/core/preview-registry.js +0 -1
- package/dist/core/review/ticket-filter.d.ts +1 -1
- package/dist/core/review/ticket-filter.js +2 -8
- package/dist/core/runtime/broker/read-ops.js +3 -0
- package/dist/core/runtime/broker.js +19 -10
- package/dist/core/runtime/package-health.js +1 -1
- package/dist/core/termrender/termrender.d.ts +1 -1
- package/dist/core/termrender/termrender.js +2 -2
- package/dist/core/tui/page-host.d.ts +1 -1
- package/dist/core/tui/page-host.js +2 -2
- package/dist/core/user-settings.d.ts +19 -0
- package/dist/core/user-settings.js +22 -0
- package/dist/daemon/api/handlers/bash-jobs.d.ts +2 -0
- package/dist/daemon/api/handlers/bash-jobs.js +89 -0
- package/dist/daemon/api/handlers/human.js +16 -14
- package/dist/daemon/api/handlers/inbox.js +311 -227
- package/dist/daemon/api/router.d.ts +5 -1
- package/dist/daemon/api/router.js +5 -0
- package/dist/daemon/api/server.js +2 -0
- package/dist/daemon/cron-run.js +23 -10
- package/dist/daemon/human/finish.d.ts +8 -3
- package/dist/daemon/human/finish.js +35 -15
- package/dist/pages/bundle.css +1 -0
- package/dist/pages/bundle.js +1347 -0
- package/dist/pages/comments.d.ts +34 -0
- package/dist/pages/comments.js +72 -0
- package/dist/pages/elements/cards.d.ts +17 -0
- package/dist/pages/elements/cards.js +785 -0
- package/dist/pages/elements/chart.d.ts +1 -0
- package/dist/pages/elements/chart.js +648 -0
- package/dist/pages/elements/options.d.ts +21 -0
- package/dist/pages/elements/options.js +604 -0
- package/dist/pages/elements/pages.d.ts +24 -0
- package/dist/pages/elements/pages.js +355 -0
- package/dist/pages/elements/slot.d.ts +21 -0
- package/dist/pages/elements/slot.js +105 -0
- package/dist/pages/elements/table.d.ts +19 -0
- package/dist/pages/elements/table.js +906 -0
- package/dist/pages/elements/text.d.ts +25 -0
- package/dist/pages/elements/text.js +951 -0
- package/dist/pages/entry.d.ts +8 -0
- package/dist/pages/entry.js +8 -0
- package/dist/pages/host.d.ts +132 -0
- package/dist/pages/host.js +149 -0
- package/dist/pages/register.d.ts +2 -0
- package/dist/pages/register.js +3 -0
- package/dist/pages/slot-config.d.ts +36 -0
- package/dist/pages/slot-config.js +50 -0
- package/dist/pages/types.d.ts +98 -0
- package/dist/pages/types.js +23 -0
- package/dist/pi-extensions/__tests__/canvas-structured-output.test.js +13 -5
- package/dist/pi-extensions/canvas-structured-output.js +7 -3
- package/dist/types.d.ts +4 -0
- package/dist/types.js +2 -0
- package/package.json +8 -8
- package/runtime.lock.json +156 -101
- package/dist/clients/inbox/deck-adapter.d.ts +0 -29
- package/dist/clients/inbox/deck-adapter.js +0 -63
- package/dist/core/human/deck-factories.d.ts +0 -8
- package/dist/core/human/deck-factories.js +0 -15
- package/dist/core/human/deck-schema.d.ts +0 -70
- package/dist/core/human/deck-schema.js +0 -92
|
@@ -3,19 +3,29 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync,
|
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
|
-
import {
|
|
6
|
+
import { submitPage, submitReview, completePage, cancelTicket, readTicketResult } from '../../tickets.js';
|
|
7
7
|
import { claimTicket } from '../../claim.js';
|
|
8
8
|
import { scanInbox } from '../../scan.js';
|
|
9
9
|
import { atomicWriteJson, progressPath, responsePath } from '../../convention.js';
|
|
10
|
-
import { validateDeck } from '../../deck-schema.js';
|
|
11
10
|
import { ticketDir } from '../../root.js';
|
|
12
11
|
const temp = mkdtempSync(join(tmpdir(), 'crtr-inbox-core-'));
|
|
13
12
|
process.env['CRTR_HOME'] = temp;
|
|
14
13
|
const source = join(temp, 'source.md');
|
|
15
14
|
writeFileSync(source, '# Source\n');
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
const pageSource = join(temp, 'page.md');
|
|
16
|
+
writeFileSync(pageSource, [
|
|
17
|
+
'---',
|
|
18
|
+
'title: A page',
|
|
19
|
+
'subtitle: Go? requires your attention.',
|
|
20
|
+
'---',
|
|
21
|
+
'',
|
|
22
|
+
'```slot:text',
|
|
23
|
+
'{ "id": "go", "initialText": "", "editable": true }',
|
|
24
|
+
'```',
|
|
25
|
+
'',
|
|
26
|
+
].join('\n'));
|
|
27
|
+
const page = (id, blockedSince) => submitPage({ dir: ticketDir(id), sourceFile: pageSource, dialect: 'md', source: { blockedSince, nodeId: 'node-1' } });
|
|
28
|
+
const answer = { go: { text: 'yes', edited: true, comments: [] } };
|
|
19
29
|
async function waitFor(paths) {
|
|
20
30
|
for (let attempts = 0; !paths.every(existsSync); attempts++) {
|
|
21
31
|
if (attempts > 1_000)
|
|
@@ -23,60 +33,60 @@ async function waitFor(paths) {
|
|
|
23
33
|
await new Promise((resolvePromise) => setTimeout(resolvePromise, 1));
|
|
24
34
|
}
|
|
25
35
|
}
|
|
26
|
-
|
|
27
|
-
const
|
|
36
|
+
page('z-last', '2025-01-01T00:00:00.000Z');
|
|
37
|
+
const newPage = page('a-first', '2025-01-02T00:00:00.000Z');
|
|
28
38
|
const review = submitReview({ dir: ticketDir('review'), review: { file: source, title: 'Review source', subtitle: 'Review findings require your decision.', source: { nodeId: 'node-1' }, blockedSince: '2025-01-02T00:00:00.000Z' } });
|
|
29
39
|
assert.throws(() => submitReview({ dir: ticketDir('missing-review-subtitle'), review: { file: source, title: 'Missing subtitle', source: {} } }), /subtitle/, 'a review without an authored subtitle is invalid');
|
|
30
40
|
assert.throws(() => submitReview({ dir: ticketDir('bad-review'), review: { file: join(temp, 'missing.md'), title: 'Bad', subtitle: 'This invalid review must be rejected.', source: {} } }), /existing absolute path/);
|
|
31
41
|
assert.equal(existsSync(ticketDir('bad-review')), false, 'invalid submission does not strand an id');
|
|
32
42
|
const prepared = ticketDir('prepared');
|
|
33
43
|
mkdirSync(prepared);
|
|
34
|
-
writeFileSync(join(prepared, '
|
|
35
|
-
const preparedTicket =
|
|
44
|
+
writeFileSync(join(prepared, 'asset.png'), 'prepared asset');
|
|
45
|
+
const preparedTicket = submitPage({ dir: prepared, sourceFile: pageSource, dialect: 'md', source: { blockedSince: '2025-01-02T00:00:00.000Z', nodeId: 'node-1' } });
|
|
36
46
|
assert.equal(preparedTicket.dir, realpathSync(prepared), 'submission accepts a crouter-precreated direct child and colocated assets');
|
|
37
47
|
// Same timestamp is stable by id; progress is resumable work, never a hidden lease.
|
|
38
|
-
atomicWriteJson(progressPath(
|
|
48
|
+
atomicWriteJson(progressPath(newPage.dir), { kind: 'page', responses: {}, savedAt: new Date().toISOString() });
|
|
39
49
|
const scanned = scanInbox();
|
|
40
50
|
assert.deepEqual(scanned.map((item) => item.id), ['a-first', 'prepared', 'review', 'z-last']);
|
|
41
51
|
assert.equal(scanned.find((item) => item.id === 'a-first')?.claim, undefined);
|
|
42
52
|
assert.equal(scanned.find((item) => item.id === 'review')?.kind, 'review');
|
|
43
53
|
assert.equal(scanned.find((item) => item.id === 'review')?.subtitle, 'Review findings require your decision.', 'review scanning exposes its required subtitle');
|
|
44
|
-
const firstClaim = claimTicket(
|
|
54
|
+
const firstClaim = claimTicket(newPage.dir);
|
|
45
55
|
assert.notEqual(firstClaim, null);
|
|
46
|
-
assert.equal(claimTicket(
|
|
56
|
+
assert.equal(claimTicket(newPage.dir), null, 'a second controller cannot claim a live ticket');
|
|
47
57
|
// A separate process writes a genuine remote claim whose heartbeat is already
|
|
48
58
|
// stale, then exits; this process recovers it — cross-process stale recovery.
|
|
49
|
-
const staleDir =
|
|
59
|
+
const staleDir = page('stale', '2025-01-03T00:00:00.000Z').dir;
|
|
50
60
|
const staleClaimReady = join(temp, 'stale-claim-ready');
|
|
51
61
|
await new Promise((resolveWorker, rejectWorker) => spawn(process.execPath, ['--conditions=crtr-src', '--import', 'tsx', 'src/core/human/__tests__/inbox-stale-claim-worker.ts', staleDir, staleClaimReady], { cwd: process.cwd(), stdio: ['ignore', 'ignore', 'inherit'] }).once('exit', (code) => code === 0 ? resolveWorker() : rejectWorker(new Error('stale-claim worker failed'))));
|
|
52
62
|
assert.notEqual(JSON.parse(readFileSync(staleClaimReady, 'utf8')), null, 'a separate process wrote a stale remote claim on disk');
|
|
53
63
|
assert.notEqual(claimTicket(staleDir), null, 'a remote claim written by another process recovers once its heartbeat has expired');
|
|
54
|
-
const malformedDir =
|
|
64
|
+
const malformedDir = page('malformed-claim', '2025-01-03T00:00:00.000Z').dir;
|
|
55
65
|
writeFileSync(join(malformedDir, 'claim.json'), '{');
|
|
56
66
|
assert.notEqual(claimTicket(malformedDir), null, 'partial claim crash artifact recovers');
|
|
57
|
-
const final =
|
|
67
|
+
const final = completePage(newPage.dir, answer, firstClaim.token);
|
|
58
68
|
assert.equal(final.won, true);
|
|
59
|
-
assert.equal(readTicketResult(
|
|
60
|
-
assert.equal(claimTicket(
|
|
69
|
+
assert.equal(readTicketResult(newPage.dir)?.kind, 'page');
|
|
70
|
+
assert.equal(claimTicket(newPage.dir), null, 'terminal tickets cannot be claimed');
|
|
61
71
|
const exactResults = [
|
|
62
|
-
{ schema: '
|
|
72
|
+
{ schema: 'crtr.page-response/v1', kind: 'page', responses: {}, summary: '', completedAt: '2025-01-04T00:00:00.000Z' },
|
|
63
73
|
{ schema: 'humanloop.review-response/v1', kind: 'review', result: { file: source, submitted: true, approved: true, comments: [], submittedAt: '2025-01-04T00:00:00.000Z', savedAt: '2025-01-04T00:00:00.000Z', reviewId: 'review-1', changed: false, baselineSha256: 'baseline', approvedSha256: 'approved', companionNodeId: 'companion-1', commentsTotal: 0, commentsUnresolved: 0 }, completedAt: '2025-01-04T00:00:00.000Z' },
|
|
64
74
|
{ schema: 'humanloop.cancel/v1', kind: 'canceled', canceledAt: '2025-01-04T00:00:00.000Z', reason: 'stop', actor: 'test' },
|
|
65
75
|
];
|
|
66
76
|
for (const [index, result] of exactResults.entries()) {
|
|
67
|
-
const dir =
|
|
77
|
+
const dir = page(`strict-${index}`, '2025-01-03T00:00:00.000Z').dir;
|
|
68
78
|
writeFileSync(responsePath(dir), JSON.stringify(result));
|
|
69
79
|
assert.equal(readTicketResult(dir)?.kind, result.kind, `strict decoder accepts exact ${result.kind} result`);
|
|
70
80
|
}
|
|
71
81
|
const validFeedback = exactResults[1].result;
|
|
72
82
|
const iso = '2025-01-04T00:00:00.000Z';
|
|
73
83
|
const rejectFixtures = [
|
|
74
|
-
{ why: 'a wrong kind for a known schema', result: { schema: '
|
|
75
|
-
{ why: 'an unknown schema id', result: { schema: '
|
|
76
|
-
{ why: 'a
|
|
77
|
-
{ why: 'a
|
|
78
|
-
{ why: 'a
|
|
79
|
-
{ why: 'a
|
|
84
|
+
{ why: 'a wrong kind for a known schema', result: { schema: 'crtr.page-response/v1', kind: 'review', responses: {}, summary: '', completedAt: iso } },
|
|
85
|
+
{ why: 'an unknown schema id', result: { schema: 'crtr.page-response/v9', kind: 'page', responses: {}, summary: '', completedAt: iso } },
|
|
86
|
+
{ why: 'a page missing its required completedAt', result: { schema: 'crtr.page-response/v1', kind: 'page', responses: {}, summary: '' } },
|
|
87
|
+
{ why: 'a page with a non-ISO completedAt', result: { schema: 'crtr.page-response/v1', kind: 'page', responses: {}, summary: '', completedAt: 'now' } },
|
|
88
|
+
{ why: 'a page whose responses are not a slot-keyed map', result: { schema: 'crtr.page-response/v1', kind: 'page', responses: [{ id: 'go' }], summary: '', completedAt: iso } },
|
|
89
|
+
{ why: 'a page with an extra top-level field', result: { schema: 'crtr.page-response/v1', kind: 'page', responses: {}, summary: '', completedAt: iso, extra: true } },
|
|
80
90
|
{ why: 'a review missing its required result', result: { schema: 'humanloop.review-response/v1', kind: 'review', completedAt: iso } },
|
|
81
91
|
{ why: 'a review whose nested result is not submitted', result: { schema: 'humanloop.review-response/v1', kind: 'review', result: { ...validFeedback, submitted: false }, completedAt: iso } },
|
|
82
92
|
{ why: 'a review with incomplete comment columns', result: { schema: 'humanloop.review-response/v1', kind: 'review', result: { ...validFeedback, approved: true, comments: [{ id: 'c1', line: 1, endLine: 1, colStart: 1, lineText: '# Source', comment: 'x', createdAt: iso }] }, completedAt: iso } },
|
|
@@ -87,29 +97,29 @@ const rejectFixtures = [
|
|
|
87
97
|
{ why: 'a canceled result with an extra top-level field', result: { schema: 'humanloop.cancel/v1', kind: 'canceled', canceledAt: iso, extra: true } },
|
|
88
98
|
];
|
|
89
99
|
for (const [index, fixture] of rejectFixtures.entries()) {
|
|
90
|
-
const dir =
|
|
100
|
+
const dir = page(`strict-reject-${index}`, '2025-01-03T00:00:00.000Z').dir;
|
|
91
101
|
writeFileSync(responsePath(dir), JSON.stringify(fixture.result));
|
|
92
102
|
assert.equal(readTicketResult(dir), null, `strict decoder rejects ${fixture.why}`);
|
|
93
103
|
}
|
|
94
104
|
const protocolDir = ticketDir('stale-protocol');
|
|
95
105
|
mkdirSync(protocolDir);
|
|
96
106
|
writeFileSync(responsePath(protocolDir), '{}');
|
|
97
|
-
assert.throws(() =>
|
|
98
|
-
const cancelDir =
|
|
107
|
+
assert.throws(() => submitPage({ dir: protocolDir, sourceFile: pageSource, dialect: 'md' }), /protocol state/, 'precreated assets may not include humanloop lifecycle state');
|
|
108
|
+
const cancelDir = page('cancel-race', '2025-01-03T00:00:00.000Z').dir;
|
|
99
109
|
const canceled = cancelTicket(cancelDir, { reason: 'stop' });
|
|
100
110
|
assert.equal(canceled.status, 'canceled');
|
|
101
111
|
assert.equal(cancelTicket(cancelDir).status, 'already_resolved', 'first final writer wins');
|
|
102
|
-
const finalRaceDir =
|
|
112
|
+
const finalRaceDir = page('final-race', '2025-01-03T00:00:00.000Z').dir;
|
|
103
113
|
const finalRaceClaim = claimTicket(finalRaceDir);
|
|
104
114
|
const finalBarrier = join(temp, 'final-race-barrier');
|
|
105
115
|
const finalReady = join(temp, 'final-race-ready');
|
|
106
116
|
const cancelWorker = spawn(process.execPath, ['--conditions=crtr-src', '--import', 'tsx', 'src/core/human/__tests__/inbox-cancel-worker.ts', finalRaceDir, finalBarrier, finalReady], { cwd: process.cwd(), stdio: ['ignore', 'pipe', 'pipe'] });
|
|
107
117
|
await waitFor([finalReady]);
|
|
108
118
|
writeFileSync(finalBarrier, 'go');
|
|
109
|
-
const finalRace =
|
|
119
|
+
const finalRace = completePage(finalRaceDir, answer, finalRaceClaim.token);
|
|
110
120
|
await new Promise((resolveWorker, rejectWorker) => cancelWorker.once('exit', (code) => code === 0 ? resolveWorker() : rejectWorker(new Error('cancel worker failed'))));
|
|
111
|
-
assert.equal(readTicketResult(finalRaceDir)?.kind, finalRace.won ? '
|
|
112
|
-
const concurrentDir =
|
|
121
|
+
assert.equal(readTicketResult(finalRaceDir)?.kind, finalRace.won ? 'page' : 'canceled', 'submit and cancellation race has one canonical winner');
|
|
122
|
+
const concurrentDir = page('concurrent-claim', '2025-01-03T00:00:00.000Z').dir;
|
|
113
123
|
const claimBarrier = join(temp, 'claim-race-barrier');
|
|
114
124
|
// Both workers block on this release until the parent has read both results, so
|
|
115
125
|
// neither winner's pid dies during the peer's attempt (a dead same-host pid is
|
package/dist/core/human/claim.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { hostname } from 'node:os';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
|
-
import { claimPath,
|
|
4
|
+
import { claimPath, pageManifestPath, responsePath, reviewPath } from './convention.js';
|
|
5
5
|
import { readJsonOrNull } from '../fs-utils.js';
|
|
6
6
|
import { withExclusiveDirectoryLock } from '../exclusive-lock.js';
|
|
7
7
|
const REMOTE_STALE_MS = 30_000;
|
|
@@ -30,7 +30,7 @@ export function isStaleClaim(claim, now = Date.now(), localHost = hostname()) {
|
|
|
30
30
|
/** Acquire a visible claim only for a still-pending ticket. Malformed crash artifacts are stale claims. */
|
|
31
31
|
export function claimTicket(dir, opts = {}) {
|
|
32
32
|
return withExclusiveDirectoryLock(`${dir}/.ticket-lock`, () => {
|
|
33
|
-
if (existsSync(responsePath(dir)) || (!existsSync(
|
|
33
|
+
if (existsSync(responsePath(dir)) || (!existsSync(pageManifestPath(dir)) && !existsSync(reviewPath(dir))))
|
|
34
34
|
return null;
|
|
35
35
|
const path = claimPath(dir);
|
|
36
36
|
const existing = readTicketClaim(dir);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type { PageManifest } from './page-schema.js';
|
|
2
|
+
/** The authored page source, colocated with its derived manifest in the bridge node directory. */
|
|
3
|
+
export declare function pagePath(dir: string, dialect: PageManifest['dialect']): string;
|
|
4
|
+
export declare function pageManifestPath(dir: string): string;
|
|
3
5
|
export declare function reviewPath(dir: string): string;
|
|
4
6
|
export declare function responsePath(dir: string): string;
|
|
5
7
|
export declare function progressPath(dir: string): string;
|
|
@@ -13,6 +15,4 @@ export declare function requireTicket(dir: string): string;
|
|
|
13
15
|
export declare function atomicWriteJson(path: string, value: unknown): void;
|
|
14
16
|
/** Publish one immutable JSON record without replacing an existing winner. */
|
|
15
17
|
export declare function publishJsonExclusive(path: string, value: unknown): boolean;
|
|
16
|
-
export declare function writeResponse(dir: string, responses: InteractionResponse[], completedAt: string, deck?: Deck): string;
|
|
17
|
-
export declare function writeProgress(dir: string, responses: InteractionResponse[]): void;
|
|
18
18
|
export declare function clearProgress(dir: string): void;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { existsSync, linkSync, mkdirSync, realpathSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
|
-
|
|
4
|
-
export function
|
|
3
|
+
/** The authored page source, colocated with its derived manifest in the bridge node directory. */
|
|
4
|
+
export function pagePath(dir, dialect) { return `${dir}/page.${dialect}`; }
|
|
5
|
+
export function pageManifestPath(dir) { return `${dir}/page.json`; }
|
|
5
6
|
export function reviewPath(dir) { return `${dir}/review.json`; }
|
|
6
7
|
export function responsePath(dir) { return `${dir}/response.json`; }
|
|
7
8
|
export function progressPath(dir) { return `${dir}/progress.json`; }
|
|
8
9
|
export function claimPath(dir) { return `${dir}/claim.json`; }
|
|
9
10
|
export function interactionState(dir) {
|
|
10
|
-
if (!existsSync(
|
|
11
|
+
if (!existsSync(pageManifestPath(dir)) && !existsSync(reviewPath(dir)))
|
|
11
12
|
return 'missing';
|
|
12
13
|
if (existsSync(responsePath(dir)))
|
|
13
14
|
return 'resolved';
|
|
@@ -18,8 +19,8 @@ export function isClaimed(dir) { return existsSync(claimPath(dir)); }
|
|
|
18
19
|
/** Resolve and verify a ticket directory without a registry. */
|
|
19
20
|
export function requireTicket(dir) {
|
|
20
21
|
const canonicalDir = realpathSync(dir);
|
|
21
|
-
if (!existsSync(
|
|
22
|
-
throw new Error('ticket directory has no
|
|
22
|
+
if (!existsSync(pageManifestPath(canonicalDir)) && !existsSync(reviewPath(canonicalDir))) {
|
|
23
|
+
throw new Error('ticket directory has no page or review');
|
|
23
24
|
}
|
|
24
25
|
return canonicalDir;
|
|
25
26
|
}
|
|
@@ -47,14 +48,6 @@ export function publishJsonExclusive(path, value) {
|
|
|
47
48
|
unlinkSync(tmp);
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
|
-
export function writeResponse(dir, responses, completedAt, deck) {
|
|
51
|
-
const summary = deck === undefined ? '' : buildSummary(deck, responses);
|
|
52
|
-
atomicWriteJson(responsePath(dir), { schema: 'humanloop.response/v2', kind: 'deck', responses, summary, completedAt });
|
|
53
|
-
return responsePath(dir);
|
|
54
|
-
}
|
|
55
|
-
export function writeProgress(dir, responses) {
|
|
56
|
-
atomicWriteJson(progressPath(dir), { kind: 'deck', responses, savedAt: new Date().toISOString() });
|
|
57
|
-
}
|
|
58
51
|
export function clearProgress(dir) {
|
|
59
52
|
try {
|
|
60
53
|
unlinkSync(progressPath(dir));
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Escape text for an HTML text node. */
|
|
2
|
+
export declare function escapeHtmlText(raw: string): string;
|
|
3
|
+
/** Escape a value for a double-quoted HTML attribute. */
|
|
4
|
+
export declare function escapeHtmlAttribute(raw: string): string;
|
|
5
|
+
/** Render one markdown document fragment as HTML. */
|
|
6
|
+
export declare function renderMarkdownToHtml(markdown: string): string;
|
|
7
|
+
/** Render inline markdown. Exported for callers rendering a single line. */
|
|
8
|
+
export declare function renderInline(source: string): string;
|