@jjh32456/dsh-longlive 1.6.7 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +70 -1508
- package/LICENSE +1 -1
- package/QUICKSTART.md +34 -13
- package/README.en.md +18 -7
- package/README.md +34 -9
- package/SPEC.md +5 -5
- package/cordis.patch.yml +23 -9
- package/docs/ANSWER-GUARD.md +92 -0
- package/docs/ARCHITECTURE.md +4 -3
- package/docs/AUTO-RECALL-WIRING.md +1 -1
- package/docs/AUTO-RECALL.md +15 -9
- package/docs/CONFIG.md +168 -35
- package/docs/ERRORS.md +17 -5
- package/docs/FAQ.md +7 -7
- package/docs/FEATURES.md +96 -28
- package/docs/MIGRATION-1.6.0.md +2 -2
- package/docs/OPERATIONS.md +6 -6
- package/docs/PANEL-INTEGRATION.md +6 -0
- package/docs/PRIVACY.md +88 -0
- package/docs/QUICKSTART.md +5 -5
- package/docs/README.en.md +4 -2
- package/docs/RESIDENT-WATCHER.md +1 -1
- package/docs/SESSION-WATCH.md +6 -6
- package/docs/TROUBLESHOOTING.md +4 -4
- package/package.json +47 -117
- package/runbook.md +2 -2
- package/scripts/backfill-auto-cards.mjs +98 -0
- package/scripts/install-surgery-task.ps1 +5 -2
- package/scripts/lib/session-watch-fixture.mjs +1 -1
- package/scripts/longlive-doctor.mjs +6 -0
- package/scripts/migrate-180.mjs +163 -0
- package/scripts/recall-index-build.mjs +128 -0
- package/scripts/session-watch-drill.mjs +1 -1
- package/scripts/surgery-run.cmd +1 -1
- package/scripts/unattended-surgery.ps1 +5 -2
- package/scripts/verify-card-anchors.mjs +138 -0
- package/src/actions/audit.js +125 -0
- package/src/actions/evidence.js +122 -0
- package/src/actions/fs.js +162 -0
- package/src/activity/index.js +760 -0
- package/src/activity/model.js +351 -0
- package/src/activity/store.js +644 -0
- package/src/activity/thinking.js +176 -0
- package/src/adapters/constants.js +11 -0
- package/src/adapters/engram/data/aliases.json +51 -0
- package/src/adapters/engram/data/policy.json +8 -0
- package/src/adapters/engram/data/routing-rules.json +546 -0
- package/src/adapters/engram/index.js +235 -0
- package/src/adapters/registry.js +315 -0
- package/src/answer/audit.js +193 -0
- package/src/answer/evidence.js +30 -0
- package/src/answer/guard.js +1370 -0
- package/src/answer/index.js +1067 -0
- package/src/answer/query.js +111 -0
- package/src/answer/second-hop.mjs +101 -0
- package/src/auto-truncate.js +1 -1
- package/src/config.js +171 -47
- package/src/deps.js +6 -4
- package/src/features.js +182 -6
- package/src/heartbeat.js +156 -23
- package/src/identity/handover.js +52 -1
- package/src/identity/index.js +13 -5
- package/src/identity/inject-position.js +2 -2
- package/src/index.js +97 -7
- package/src/lifecycle/gap-clocks.js +328 -0
- package/src/lifecycle/index.js +269 -0
- package/src/live.js +22 -2
- package/src/memory/activity-capture.js +229 -0
- package/src/memory/anchors.js +194 -0
- package/src/memory/audit-records.js +222 -0
- package/src/memory/auto-cards.js +401 -0
- package/src/memory/capture.js +442 -0
- package/src/memory/card-anchors.js +203 -0
- package/src/memory/conflicts.js +106 -0
- package/src/memory/decay.js +311 -0
- package/src/memory/distill.js +471 -0
- package/src/memory/event-index.js +917 -0
- package/src/memory/evidence-chain.js +295 -0
- package/src/memory/evidence.js +145 -0
- package/src/memory/filter-runner.js +1 -1
- package/src/memory/filter.js +5 -1
- package/src/memory/forget-v2.js +410 -0
- package/src/memory/forget.js +71 -0
- package/src/memory/frames-activation.js +247 -0
- package/src/memory/fts-query.js +174 -0
- package/src/memory/governance.js +604 -0
- package/src/memory/index.js +1 -0
- package/src/memory/l2-gate.js +169 -0
- package/src/memory/lock.js +121 -0
- package/src/memory/migrate.js +139 -0
- package/src/memory/recall-v2.js +223 -0
- package/src/memory/recall.js +65 -2
- package/src/memory/records.js +425 -0
- package/src/memory/retention.js +128 -0
- package/src/memory/review.js +124 -0
- package/src/memory/trust.js +88 -0
- package/src/memory/version-chain.js +264 -0
- package/src/memory/wal.js +137 -0
- package/src/memory/working-frame.js +516 -0
- package/src/metrics/classify.mjs +5 -3
- package/src/metrics/index.mjs +21 -1
- package/src/metrics/prestep-latency.mjs +300 -0
- package/src/metrics/store.mjs +25 -0
- package/src/policy-profile.js +18 -3
- package/src/recall/anaphora.mjs +87 -0
- package/src/recall/cards.mjs +7 -0
- package/src/recall/embedding.mjs +8 -8
- package/src/recall/entities.mjs +112 -0
- package/src/recall/index.js +235 -19
- package/src/recall/inject-order.mjs +10 -8
- package/src/recall/privacy.mjs +105 -16
- package/src/recall/recall-core.mjs +116 -10
- package/src/recall/shared-visibility.js +302 -0
- package/src/recall/time-normalizer.mjs +196 -0
- package/src/role/activation.js +191 -0
- package/src/role/attention.js +318 -0
- package/src/role/base.js +36 -0
- package/src/role/channels.js +423 -0
- package/src/role/index.js +425 -0
- package/src/role/interfaces.js +115 -0
- package/src/role/personality.js +370 -0
- package/src/role/readers.js +151 -0
- package/src/role/relationships.js +477 -0
- package/src/role/store.js +202 -0
- package/src/role/writeprotect.js +373 -0
- package/src/router/aliases.json +176 -25
- package/src/router/background/agent-runner.mjs +1 -1
- package/src/router/background/anchor.mjs +1 -1
- package/src/router/background/audit.mjs +2 -2
- package/src/router/background/budget-ledger.mjs +2 -2
- package/src/router/background/host-sim.mjs +1 -1
- package/src/router/background/memory.mjs +4 -4
- package/src/router/background/queue.mjs +1 -1
- package/src/router/background/retry.mjs +1 -1
- package/src/router/background/router.mjs +1 -1
- package/src/router/background/stream.mjs +1 -1
- package/src/router/background/tool-policy.mjs +1 -1
- package/src/router/design-headings.json +5 -5
- package/src/router/face/config.mjs +1 -1
- package/src/router/face/identity.mjs +3 -3
- package/src/router/judge.mjs +8 -7
- package/src/router/policies/generic.json +21 -4
- package/src/router/route.js +109 -22
- package/src/router/router.mjs +10 -6
- package/src/router/routing-rules.json +260 -445
- package/src/router/run-sim.mjs +8 -8
- package/src/router/runtime.js +23 -12
- package/src/router/service/board.mjs +95 -15
- package/src/router/service/config.mjs +5 -5
- package/src/router/service/context.mjs +1 -1
- package/src/router/service/hint.mjs +14 -0
- package/src/router/service/queue.mjs +1 -1
- package/src/router/service/service.mjs +27 -11
- package/src/router/shadow.mjs +4 -3
- package/src/surgery-recovery.js +2 -2
- package/src/tool-output.js +2 -2
- package/src/tools.js +39 -7
- package/src/truncate-visible.js +2 -2
- package/src/ui/actions.js +1312 -0
- package/src/ui/panel-api.js +367 -0
- package/src/ui/panel-auth.js +251 -0
- package/src/ui/panel-integrity.js +243 -0
- package/src/ui/routes-readonly.js +695 -0
- package/src/ui/routes.js +3 -0
- package/docs/DEV.md +0 -279
- package/docs/PRESENCE-BUS-WIRING.md +0 -491
- package/docs/PRESENCE-BUS.md +0 -694
- package/docs/SURGERY.md +0 -186
- package/scripts/mark-core-cards.mjs +0 -289
- package/scripts/pack.mjs +0 -392
- package/scripts/router-proposals.mjs +0 -386
- package/src/router/fixtures/M1.json +0 -16
- package/src/router/fixtures/M2.json +0 -16
- package/src/router/fixtures/M3.json +0 -20
- package/src/router/fixtures/M4.json +0 -20
- package/src/router/fixtures/M5.json +0 -45
- package/src/router/fixtures/M6.json +0 -53
- package/src/router/fixtures/M7.json +0 -46
- package/src/router/fixtures/S1.json +0 -20
- package/src/router/fixtures/S2.json +0 -23
- package/src/router/fixtures/S3.json +0 -20
- package/src/router/fixtures/S4.json +0 -34
- package/src/router/fixtures/S5.json +0 -43
- package/src/router/fixtures/S6.json +0 -47
- package/src/router/fixtures/T1.json +0 -23
- package/src/router/fixtures/T2.json +0 -16
- package/src/router/fixtures/T3.json +0 -19
- package/src/router/fixtures/T4.json +0 -16
- package/src/router/fixtures/X1.json +0 -17
- package/src/router/fixtures/X2.json +0 -20
- package/src/router/gen-design-headings.mjs +0 -107
- package/src/router/policies/owner-rulings-2026-11-03.json +0 -62
- package/test/auto-recall.test.mjs +0 -474
- package/test/auto-truncate.test.mjs +0 -931
- package/test/autobind.test.mjs +0 -385
- package/test/bus.test.mjs +0 -1572
- package/test/cli-guard.test.mjs +0 -198
- package/test/conclusion.test.mjs +0 -408
- package/test/config-schema.test.mjs +0 -204
- package/test/d6-output.test.mjs +0 -194
- package/test/d6.test.mjs +0 -395
- package/test/deps.test.mjs +0 -342
- package/test/doctor-setup.test.mjs +0 -297
- package/test/embedding-recall.test.mjs +0 -347
- package/test/feature-flags.test.mjs +0 -568
- package/test/filter-shadow.test.mjs +0 -170
- package/test/fixtures/baseline-0.8.0-registration.json +0 -556
- package/test/fixtures/d6-header-tools.json +0 -1
- package/test/fixtures/lock-child.mjs +0 -67
- package/test/fixtures/pre-step-container.mjs +0 -100
- package/test/fixtures/privacy-memory/topics/pii-unmarked.md +0 -9
- package/test/fixtures/privacy-memory/topics/secret-local-only.md +0 -11
- package/test/fixtures/profile-session-longlive-config.json +0 -20
- package/test/fixtures/proposal-cas-child.mjs +0 -66
- package/test/host-engine-status.test.mjs +0 -307
- package/test/hot-truncate.test.mjs +0 -1349
- package/test/identity-card-marker.test.mjs +0 -307
- package/test/identity-router/run.mjs +0 -721
- package/test/integration-pre-step.test.mjs +0 -553
- package/test/live.test.mjs +0 -1020
- package/test/maintenance-schedule.test.mjs +0 -538
- package/test/memory.test.mjs +0 -2828
- package/test/metrics.test.mjs +0 -478
- package/test/orchestrator.test.mjs +0 -266
- package/test/p1_probe.mjs +0 -97
- package/test/packaging.test.mjs +0 -182
- package/test/portability.test.mjs +0 -343
- package/test/rc4-regressions.test.mjs +0 -562
- package/test/rc5-regressions.test.mjs +0 -197
- package/test/rc6-chinese.test.mjs +0 -394
- package/test/router-1100/apply-off.test.mjs +0 -175
- package/test/router-1100/budget-reconcile.test.mjs +0 -215
- package/test/router-1100/config-context.test.mjs +0 -211
- package/test/router-1100/helpers.mjs +0 -129
- package/test/router-1100/hint-envelope.test.mjs +0 -150
- package/test/router-1100/lock-crossproc.test.mjs +0 -206
- package/test/router-1100/proposal-cas-crossproc.test.mjs +0 -184
- package/test/router-1100/router-apply.test.mjs +0 -372
- package/test/router-1100/router-fixb.test.mjs +0 -602
- package/test/router-1100/router-proposals.test.mjs +0 -405
- package/test/router-1100/router-queue-lease-wal.test.mjs +0 -355
- package/test/router-1100/router-security-cr.test.mjs +0 -333
- package/test/router-1100/service-lifecycle.test.mjs +0 -220
- package/test/router-1100/tools-contract.test.mjs +0 -164
- package/test/router-1100/turn-reflect-tick.test.mjs +0 -89
- package/test/router-1100/v6-contract.test.mjs +0 -158
- package/test/router-1100/v6b-contract.test.mjs +0 -293
- package/test/router-1100/v6c-contract.test.mjs +0 -249
- package/test/router-1100/wal-rotation.test.mjs +0 -375
- package/test/router-1100/wp1-config.test.mjs +0 -170
- package/test/router-1100/wp1-default-off.test.mjs +0 -77
- package/test/router-1100/wp1-model-status.test.mjs +0 -90
- package/test/router-1100/wp1-status.test.mjs +0 -220
- package/test/router-1100/wp1-tools.test.mjs +0 -146
- package/test/router-1100/wp5-cli.test.mjs +0 -240
- package/test/router-1100/wp5-model-auto.test.mjs +0 -418
- package/test/router-1100/wp6-merge.test.mjs +0 -386
- package/test/router-1100/wp6-wake.test.mjs +0 -502
- package/test/router-background/audit.test.mjs +0 -37
- package/test/router-background/budget-ledger.test.mjs +0 -330
- package/test/router-background/cross-process.test.mjs +0 -222
- package/test/router-background/fixtures/cross-child.mjs +0 -85
- package/test/router-background/fixtures/real-shapes.mjs +0 -239
- package/test/router-background/router.test.mjs +0 -1044
- package/test/router-background/stream-retry.test.mjs +0 -358
- package/test/router-background/tool-agent.test.mjs +0 -384
- package/test/router-face/bridge.test.mjs +0 -169
- package/test/router-face/face.test.mjs +0 -316
- package/test/router-failclosed.test.mjs +0 -466
- package/test/router-identity.test.mjs +0 -384
- package/test/router-l1-l2.contract.test.mjs +0 -1075
- package/test/router-memory-tools.test.mjs +0 -262
- package/test/router-policy.test.mjs +0 -225
- package/test/router-runtime.test.mjs +0 -438
- package/test/router-shadow.test.mjs +0 -287
- package/test/router.test.mjs +0 -903
- package/test/run-rc.mjs +0 -299
- package/test/run.mjs +0 -2469
- package/test/safety-defaults.test.mjs +0 -179
- package/test/seeded-load.test.mjs +0 -436
- package/test/session-original.test.mjs +0 -338
- package/test/surgery-recovery.test.mjs +0 -936
- package/test/t7_drive.mjs +0 -151
- package/test/tool-output.test.mjs +0 -393
- package/test/tool-schema.test.mjs +0 -177
- package/test/tools_contract.test.mjs +0 -716
- package/test/truncate-visible.test.mjs +0 -1150
- package/test/ui-flags.test.mjs +0 -477
- package/test/v4-hardening.test.mjs +0 -427
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
// D3 — session_original: fetch one turn out of a session log.
|
|
2
|
-
//
|
|
3
|
-
// Every fixture is a real encoded v4 log under a throwaway DSH home. The suite
|
|
4
|
-
// covers the four acceptance cases (hit, missing turn, truncation, privacy) plus
|
|
5
|
-
// the selection and read-only edges that make the tool safe to expose:
|
|
6
|
-
// - a hit returns the tool output verbatim and reports a D2 archive path
|
|
7
|
-
// without writing it;
|
|
8
|
-
// - a missing turn / call id is a structured error, never a throw;
|
|
9
|
-
// - truncation pages by UTF-8 bytes and reassembles into the exact original;
|
|
10
|
-
// - the privacy gate scans the FULL selection, so offset/maxBytes cannot
|
|
11
|
-
// page around a match;
|
|
12
|
-
// - a corrupt log is reported as SESSION_CORRUPT.
|
|
13
|
-
|
|
14
|
-
import fs from 'node:fs';
|
|
15
|
-
import os from 'node:os';
|
|
16
|
-
import path from 'node:path';
|
|
17
|
-
import assert from 'node:assert/strict';
|
|
18
|
-
import test, { after } from 'node:test';
|
|
19
|
-
|
|
20
|
-
import { TOOLS } from '../src/tools.js';
|
|
21
|
-
import { sessionOriginal } from '../src/tools.js';
|
|
22
|
-
import { encodeSession } from '../src/codec.js';
|
|
23
|
-
|
|
24
|
-
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'longlive-d3-'));
|
|
25
|
-
|
|
26
|
-
after(() => {
|
|
27
|
-
fs.rmSync(TMP, { recursive: true, force: true });
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
function makeRoot(label) {
|
|
31
|
-
const root = path.join(TMP, label);
|
|
32
|
-
fs.mkdirSync(root, { recursive: true });
|
|
33
|
-
return root;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Build events with contiguous seq values, starting at 0. */
|
|
37
|
-
function eventBuilder() {
|
|
38
|
-
const events = [];
|
|
39
|
-
const push = (type, data) => {
|
|
40
|
-
events.push({
|
|
41
|
-
seq: events.length,
|
|
42
|
-
type,
|
|
43
|
-
time: Date.now() - 3_600_000 + events.length,
|
|
44
|
-
data,
|
|
45
|
-
});
|
|
46
|
-
};
|
|
47
|
-
return { events, push };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function writeSession(root, id, events, { corrupt = false } = {}) {
|
|
51
|
-
const dir = path.join(root, 'sessions', 'scope-d3', id);
|
|
52
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
53
|
-
const file = path.join(dir, 'session.v4.jsonl.zstd');
|
|
54
|
-
const header = {
|
|
55
|
-
type: 'session',
|
|
56
|
-
version: 4,
|
|
57
|
-
id,
|
|
58
|
-
createdAt: Date.now(),
|
|
59
|
-
cwd: root,
|
|
60
|
-
isSeeded: false,
|
|
61
|
-
delegationDepth: 0,
|
|
62
|
-
};
|
|
63
|
-
fs.writeFileSync(file, corrupt ? Buffer.from('this is not a zstd session log') : encodeSession(header, events));
|
|
64
|
-
return file;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function configFor(root) {
|
|
68
|
-
return { home: root, memoryRoot: path.join(root, 'memory') };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function recipe({ toolOutput = 'TOOL-OUTPUT', includeSecondTurn = true } = {}) {
|
|
72
|
-
const { events, push } = eventBuilder();
|
|
73
|
-
push('turn/start', { turn: 1 });
|
|
74
|
-
push('user/message', {
|
|
75
|
-
content: [{ type: 'text', text: 'question one' }],
|
|
76
|
-
source: { kind: 'user' },
|
|
77
|
-
role: 'user',
|
|
78
|
-
id: 'user-1',
|
|
79
|
-
});
|
|
80
|
-
push('assistant/message', {
|
|
81
|
-
turn: 1,
|
|
82
|
-
step: 1,
|
|
83
|
-
message: {
|
|
84
|
-
role: 'assistant',
|
|
85
|
-
content: [{ type: 'text', text: 'answer one' }, { type: 'reasoning', text: 'private chain' }],
|
|
86
|
-
},
|
|
87
|
-
usage: {},
|
|
88
|
-
stream: false,
|
|
89
|
-
});
|
|
90
|
-
push('tool/call', {
|
|
91
|
-
turn: 1,
|
|
92
|
-
step: 1,
|
|
93
|
-
callId: 'call-1',
|
|
94
|
-
name: 'demo_tool',
|
|
95
|
-
arguments: '{"query":"alpha"}',
|
|
96
|
-
});
|
|
97
|
-
push('tool/result', {
|
|
98
|
-
turn: 1,
|
|
99
|
-
step: 1,
|
|
100
|
-
message: {
|
|
101
|
-
role: 'tool',
|
|
102
|
-
source: { kind: 'tool', callId: 'call-1' },
|
|
103
|
-
toolCallId: 'call-1',
|
|
104
|
-
content: [{ type: 'text', text: toolOutput }],
|
|
105
|
-
isError: false,
|
|
106
|
-
id: 'result-1',
|
|
107
|
-
},
|
|
108
|
-
meta: {},
|
|
109
|
-
});
|
|
110
|
-
if (includeSecondTurn) {
|
|
111
|
-
push('turn/start', { turn: 2 });
|
|
112
|
-
push('user/message', {
|
|
113
|
-
content: [{ type: 'text', text: 'question two' }],
|
|
114
|
-
source: { kind: 'user' },
|
|
115
|
-
role: 'user',
|
|
116
|
-
id: 'user-2',
|
|
117
|
-
});
|
|
118
|
-
push('tool/call', {
|
|
119
|
-
turn: 2,
|
|
120
|
-
step: 1,
|
|
121
|
-
callId: 'call-2',
|
|
122
|
-
name: 'other_tool',
|
|
123
|
-
arguments: '{}',
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
return events;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
test('session_original is registered exactly once with the documented parameters', () => {
|
|
130
|
-
const matches = TOOLS.filter((tool) => tool.name === 'session_original');
|
|
131
|
-
assert.equal(matches.length, 1, 'session_original must be registered exactly once');
|
|
132
|
-
const [tool] = matches;
|
|
133
|
-
assert.equal(typeof tool.invoke, 'function');
|
|
134
|
-
assert.equal(tool.parameters.turn.type, 'integer');
|
|
135
|
-
assert.deepEqual(tool.parameters.kind.enum, ['tool', 'message', 'call', 'all']);
|
|
136
|
-
assert.equal(tool.parameters.toolCallId.type, 'string');
|
|
137
|
-
assert.equal(tool.parameters.messageIndex.type, 'integer');
|
|
138
|
-
assert.equal(tool.parameters.maxBytes.type, 'integer');
|
|
139
|
-
assert.equal(tool.parameters.offset.type, 'integer');
|
|
140
|
-
assert.equal(typeof tool.output.render, 'function');
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test('hit: returns the tool output verbatim and reports the D2 archive path without writing it', () => {
|
|
144
|
-
const root = makeRoot('hit');
|
|
145
|
-
const text = `TOOL-OUTPUT ${'A'.repeat(5000)}`;
|
|
146
|
-
const file = writeSession(root, 'hit-session', recipe({ toolOutput: text }));
|
|
147
|
-
const config = configFor(root);
|
|
148
|
-
|
|
149
|
-
const value = sessionOriginal({ sessionPath: file, turn: 1 }, config);
|
|
150
|
-
assert.equal(value.ok, true);
|
|
151
|
-
assert.equal(value.status, 'ok');
|
|
152
|
-
assert.equal(value.content, text);
|
|
153
|
-
assert.equal(value.totalBytes, Buffer.byteLength(text, 'utf8'));
|
|
154
|
-
assert.equal(value.returnedBytes, value.totalBytes);
|
|
155
|
-
assert.equal(value.truncated, false);
|
|
156
|
-
assert.equal(value.nextOffset, null);
|
|
157
|
-
assert.equal(value.selectedCount, 1);
|
|
158
|
-
assert.equal(value.selected[0].toolCallId, 'call-1');
|
|
159
|
-
assert.equal(value.selected[0].toolName, 'demo_tool');
|
|
160
|
-
assert.ok(value.archives[0].path.endsWith(path.join('tool-output', '1', 'call-1.txt')));
|
|
161
|
-
assert.equal(value.archives[0].exists, false);
|
|
162
|
-
assert.equal(fs.existsSync(path.join(config.memoryRoot, '.longlive')), false, 'read-only: no archive tree');
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
test('turn attribution: user messages with no data.turn belong to the preceding turn/start', () => {
|
|
166
|
-
const root = makeRoot('attribution');
|
|
167
|
-
const file = writeSession(root, 'attribution-session', recipe());
|
|
168
|
-
const config = configFor(root);
|
|
169
|
-
|
|
170
|
-
const first = sessionOriginal({ sessionPath: file, turn: 1, kind: 'message', messageIndex: 0 }, config);
|
|
171
|
-
assert.equal(first.ok, true);
|
|
172
|
-
assert.equal(first.content, 'question one');
|
|
173
|
-
|
|
174
|
-
const second = sessionOriginal({ sessionPath: file, turn: 2, kind: 'message', messageIndex: 0 }, config);
|
|
175
|
-
assert.equal(second.ok, true);
|
|
176
|
-
assert.equal(second.content, 'question two');
|
|
177
|
-
assert.equal(second.selected[0].seq, 6);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
test('reasoning parts are never returned as message text', () => {
|
|
181
|
-
const root = makeRoot('reasoning');
|
|
182
|
-
const file = writeSession(root, 'reasoning-session', recipe());
|
|
183
|
-
const value = sessionOriginal({ sessionPath: file, turn: 1, kind: 'message', messageIndex: 1 }, configFor(root));
|
|
184
|
-
assert.equal(value.ok, true);
|
|
185
|
-
assert.equal(value.content, 'answer one');
|
|
186
|
-
assert.equal(value.selected[0].nonTextParts, 1);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
test('missing turn: structured TURN_NOT_FOUND with the available turns', () => {
|
|
190
|
-
const root = makeRoot('missing-turn');
|
|
191
|
-
const file = writeSession(root, 'missing-turn-session', recipe());
|
|
192
|
-
const value = sessionOriginal({ sessionPath: file, turn: 99 }, configFor(root));
|
|
193
|
-
assert.equal(value.ok, false);
|
|
194
|
-
assert.equal(value.status, 'error');
|
|
195
|
-
assert.equal(value.code, 'TURN_NOT_FOUND');
|
|
196
|
-
assert.equal(value.error.code, 'TURN_NOT_FOUND');
|
|
197
|
-
assert.deepEqual(value.availableTurns, [1, 2]);
|
|
198
|
-
assert.equal(value.turnCount, 2);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
test('truncation: pages on UTF-8 boundaries and reassembles into the exact original', () => {
|
|
202
|
-
const root = makeRoot('truncate');
|
|
203
|
-
const full = `${'汉字🙂'.repeat(1500)}END`;
|
|
204
|
-
const file = writeSession(root, 'truncate-session', recipe({ toolOutput: full }));
|
|
205
|
-
const config = configFor(root);
|
|
206
|
-
|
|
207
|
-
let offset = 0;
|
|
208
|
-
let rebuilt = '';
|
|
209
|
-
let pages = 0;
|
|
210
|
-
let firstTruncated = null;
|
|
211
|
-
for (;;) {
|
|
212
|
-
const value = sessionOriginal({ sessionPath: file, turn: 1, maxBytes: 1024, offset }, config);
|
|
213
|
-
assert.equal(value.ok, true);
|
|
214
|
-
assert.ok(value.returnedBytes <= 1024, `page returned ${value.returnedBytes} bytes`);
|
|
215
|
-
assert.equal(value.content.includes('\uFFFD'), false, 'a page split a code point');
|
|
216
|
-
assert.equal(value.offset, offset);
|
|
217
|
-
rebuilt += value.content;
|
|
218
|
-
pages += 1;
|
|
219
|
-
if (firstTruncated == null) firstTruncated = value.truncated;
|
|
220
|
-
if (value.nextOffset == null) break;
|
|
221
|
-
assert.ok(value.nextOffset > offset, 'nextOffset must advance');
|
|
222
|
-
offset = value.nextOffset;
|
|
223
|
-
assert.ok(pages < 1000, 'paging did not terminate');
|
|
224
|
-
}
|
|
225
|
-
assert.equal(firstTruncated, true);
|
|
226
|
-
assert.equal(rebuilt, full);
|
|
227
|
-
assert.equal(Buffer.byteLength(rebuilt, 'utf8'), Buffer.byteLength(full, 'utf8'));
|
|
228
|
-
assert.ok(pages > 1, 'the fixture must actually need more than one page');
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
test('maxBytes below the floor is clamped, not rejected', () => {
|
|
232
|
-
const root = makeRoot('clamp');
|
|
233
|
-
const file = writeSession(root, 'clamp-session', recipe({ toolOutput: 'B'.repeat(4096) }));
|
|
234
|
-
const value = sessionOriginal({ sessionPath: file, turn: 1, maxBytes: 10 }, configFor(root));
|
|
235
|
-
assert.equal(value.ok, true);
|
|
236
|
-
assert.equal(value.maxBytes, 1024);
|
|
237
|
-
assert.equal(value.returnedBytes, 1024);
|
|
238
|
-
assert.equal(value.truncated, true);
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
test('privacy: a phone number in the FULL selection blocks every page and returns no content', () => {
|
|
242
|
-
const root = makeRoot('privacy');
|
|
243
|
-
const file = writeSession(root, 'privacy-session', recipe({ toolOutput: 'secret 13800138000 tail' }));
|
|
244
|
-
const config = configFor(root);
|
|
245
|
-
|
|
246
|
-
for (const args of [
|
|
247
|
-
{ sessionPath: file, turn: 1 },
|
|
248
|
-
{ sessionPath: file, turn: 1, offset: 3, maxBytes: 1024 },
|
|
249
|
-
{ sessionPath: file, turn: 1, maxBytes: 4096 },
|
|
250
|
-
]) {
|
|
251
|
-
const value = sessionOriginal(args, config);
|
|
252
|
-
assert.equal(value.ok, false);
|
|
253
|
-
assert.equal(value.status, 'blocked');
|
|
254
|
-
assert.equal(value.code, 'PRIVACY_BLOCKED');
|
|
255
|
-
assert.equal(Object.prototype.hasOwnProperty.call(value, 'content'), false);
|
|
256
|
-
assert.equal(value.privacy.verdict, 'local_only');
|
|
257
|
-
assert.ok(value.privacy.reasons.includes('phone'));
|
|
258
|
-
}
|
|
259
|
-
assert.equal(fs.existsSync(path.join(config.memoryRoot, '.longlive')), false);
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
test('toolCallId narrows a turn with several tool results', () => {
|
|
263
|
-
const root = makeRoot('callid');
|
|
264
|
-
const { events, push } = eventBuilder();
|
|
265
|
-
push('turn/start', { turn: 1 });
|
|
266
|
-
for (const [callId, text] of [['call-a', 'alpha output'], ['call-b', 'beta output']]) {
|
|
267
|
-
push('tool/call', { turn: 1, step: 1, callId, name: 'demo', arguments: '{}' });
|
|
268
|
-
push('tool/result', {
|
|
269
|
-
turn: 1,
|
|
270
|
-
step: 1,
|
|
271
|
-
message: {
|
|
272
|
-
role: 'tool',
|
|
273
|
-
source: { kind: 'tool', callId },
|
|
274
|
-
toolCallId: callId,
|
|
275
|
-
content: [{ type: 'text', text }],
|
|
276
|
-
},
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
const file = writeSession(root, 'callid-session', events);
|
|
280
|
-
const config = configFor(root);
|
|
281
|
-
|
|
282
|
-
const beta = sessionOriginal({ sessionPath: file, turn: 1, toolCallId: 'call-b' }, config);
|
|
283
|
-
assert.equal(beta.ok, true);
|
|
284
|
-
assert.equal(beta.content, 'beta output');
|
|
285
|
-
assert.equal(beta.selectedCount, 1);
|
|
286
|
-
|
|
287
|
-
const missing = sessionOriginal({ sessionPath: file, turn: 1, toolCallId: 'nope' }, config);
|
|
288
|
-
assert.equal(missing.ok, false);
|
|
289
|
-
assert.equal(missing.code, 'TOOL_CALL_NOT_FOUND');
|
|
290
|
-
assert.deepEqual(
|
|
291
|
-
missing.toolCalls.map((entry) => entry.toolCallId),
|
|
292
|
-
['call-a', 'call-a', 'call-b', 'call-b'],
|
|
293
|
-
);
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
test('an empty kind is a successful "empty" status, not an error', () => {
|
|
297
|
-
const root = makeRoot('empty');
|
|
298
|
-
const file = writeSession(root, 'empty-session', recipe());
|
|
299
|
-
const value = sessionOriginal({ sessionPath: file, turn: 2 }, configFor(root));
|
|
300
|
-
assert.equal(value.ok, true);
|
|
301
|
-
assert.equal(value.status, 'empty');
|
|
302
|
-
assert.equal(value.content, '');
|
|
303
|
-
assert.equal(value.selectedCount, 0);
|
|
304
|
-
assert.equal(value.truncated, false);
|
|
305
|
-
assert.equal(value.nextOffset, null);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
test('corrupt logs and bad arguments are structured errors, never throws', () => {
|
|
309
|
-
const root = makeRoot('corrupt');
|
|
310
|
-
const file = writeSession(root, 'corrupt-session', [], { corrupt: true });
|
|
311
|
-
const config = configFor(root);
|
|
312
|
-
|
|
313
|
-
const corrupt = sessionOriginal({ sessionPath: file, turn: 1 }, config);
|
|
314
|
-
assert.equal(corrupt.ok, false);
|
|
315
|
-
assert.equal(corrupt.code, 'SESSION_CORRUPT');
|
|
316
|
-
|
|
317
|
-
const badTurn = sessionOriginal({ sessionPath: file, turn: 0 }, config);
|
|
318
|
-
assert.equal(badTurn.ok, false);
|
|
319
|
-
assert.equal(badTurn.code, 'E_BAD_ARGUMENT');
|
|
320
|
-
|
|
321
|
-
const badKind = sessionOriginal({ sessionPath: file, turn: 1, kind: 'nope' }, config);
|
|
322
|
-
assert.equal(badKind.ok, false);
|
|
323
|
-
assert.equal(badKind.code, 'E_BAD_ARGUMENT');
|
|
324
|
-
|
|
325
|
-
const badSelector = sessionOriginal({ sessionPath: file, turn: 1, toolCallId: 'x', messageIndex: 0 }, config);
|
|
326
|
-
assert.equal(badSelector.ok, false);
|
|
327
|
-
assert.equal(badSelector.code, 'E_BAD_ARGUMENT');
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
test('sessionId resolution works through the normal session resolver', () => {
|
|
331
|
-
const root = makeRoot('by-id');
|
|
332
|
-
writeSession(root, 'by-id-session', recipe({ toolOutput: 'by id output' }));
|
|
333
|
-
const value = sessionOriginal({ sessionId: 'by-id-session', turn: 1 }, configFor(root));
|
|
334
|
-
assert.equal(value.ok, true);
|
|
335
|
-
assert.equal(value.content, 'by id output');
|
|
336
|
-
assert.equal(value.sessionId, 'by-id-session');
|
|
337
|
-
assert.equal(value.scope, 'scope-d3');
|
|
338
|
-
});
|