@jjh32456/dsh-longlive 1.6.8 → 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 +69 -1539
- package/LICENSE +1 -1
- package/QUICKSTART.md +33 -12
- package/README.en.md +17 -7
- package/README.md +29 -10
- package/SPEC.md +5 -5
- package/cordis.patch.yml +17 -12
- package/docs/ANSWER-GUARD.md +92 -0
- package/docs/ARCHITECTURE.md +3 -3
- package/docs/AUTO-RECALL-WIRING.md +1 -1
- package/docs/AUTO-RECALL.md +15 -9
- package/docs/CONFIG.md +143 -37
- package/docs/ERRORS.md +17 -5
- package/docs/FAQ.md +7 -7
- package/docs/FEATURES.md +66 -29
- 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 +3 -2
- package/docs/RESIDENT-WATCHER.md +1 -1
- package/docs/SESSION-WATCH.md +6 -6
- package/docs/TROUBLESHOOTING.md +4 -4
- package/package.json +42 -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 +129 -18
- package/src/activity/model.js +11 -1
- package/src/activity/store.js +22 -3
- 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 +145 -48
- package/src/deps.js +6 -4
- package/src/features.js +158 -8
- 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/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 +1 -1
- 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/config.mjs +5 -5
- package/src/router/service/context.mjs +1 -1
- package/src/router/service/queue.mjs +1 -1
- 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/activity-realtime.test.mjs +0 -959
- package/test/activity-stream.test.mjs +0 -512
- package/test/auto-recall.test.mjs +0 -474
- package/test/auto-truncate.test.mjs +0 -931
- package/test/autobind.test.mjs +0 -399
- 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 -210
- 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 -585
- package/test/filter-shadow.test.mjs +0 -170
- package/test/fixtures/activity-append-child.mjs +0 -29
- 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 -1023
- 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 -184
- 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 -1076
- 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 -315
- package/test/run.mjs +0 -2469
- package/test/safety-defaults.test.mjs +0 -181
- package/test/seeded-load.test.mjs +0 -436
- package/test/session-original.test.mjs +0 -338
- package/test/state-truth.test.mjs +0 -937
- 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
- package/test/version-consistency.test.mjs +0 -286
package/test/d6.test.mjs
DELETED
|
@@ -1,395 +0,0 @@
|
|
|
1
|
-
// D6 unit gate: the tool surface is trimmed on turn 1, frozen for the session,
|
|
2
|
-
// recoverable through tool_search, and never let a broken filter cost the
|
|
3
|
-
// session its tools. Everything runs against a fake host shaped like the two
|
|
4
|
-
// system-prompt/assemble contracts (0.2.1 returns tools in the waterfall;
|
|
5
|
-
// 0.2.2 returns them only from the provider, after the waterfall).
|
|
6
|
-
|
|
7
|
-
import assert from 'node:assert/strict';
|
|
8
|
-
import fs from 'node:fs';
|
|
9
|
-
import os from 'node:os';
|
|
10
|
-
import path from 'node:path';
|
|
11
|
-
import test from 'node:test';
|
|
12
|
-
import { fileURLToPath } from 'node:url';
|
|
13
|
-
|
|
14
|
-
import { assembleShape, selectAllowlist } from '../src/d6/surface.js';
|
|
15
|
-
import { TOOL_SEARCH_SPEC, registerD6 } from '../src/d6/runtime.js';
|
|
16
|
-
import { estimateToolsTokens } from '../src/metrics/estimate.mjs';
|
|
17
|
-
import { apply } from '../src/index.js';
|
|
18
|
-
|
|
19
|
-
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
20
|
-
const REAL_TOOLS = JSON.parse(fs.readFileSync(path.join(HERE, 'fixtures', 'd6-header-tools.json'), 'utf8'));
|
|
21
|
-
|
|
22
|
-
function tool(name, description) {
|
|
23
|
-
return {
|
|
24
|
-
name,
|
|
25
|
-
description,
|
|
26
|
-
parameters: { type: 'object', properties: { value: { type: 'string' } }, additionalProperties: false },
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const BASE = [
|
|
31
|
-
tool('read', 'Read a file from the workspace.'),
|
|
32
|
-
tool('write', 'Write a file in the workspace.'),
|
|
33
|
-
tool('edit', 'Edit a text file by replacing literal text.'),
|
|
34
|
-
tool('pwsh', 'Run a PowerShell command.'),
|
|
35
|
-
tool('glob', 'Find files by glob pattern.'),
|
|
36
|
-
tool('grep', 'Search file contents with a regex.'),
|
|
37
|
-
tool('todo_write', 'Record a task list.'),
|
|
38
|
-
tool('session_health', 'Inspect a DSH session log.'),
|
|
39
|
-
tool('session_search', 'Search prior sessions.'),
|
|
40
|
-
tool('memory_recall', 'Search memory cards.'),
|
|
41
|
-
tool('read_image', 'Read a PNG/JPEG/WebP/GIF image.'),
|
|
42
|
-
tool('send_meme', 'Send a meme image.'),
|
|
43
|
-
tool('learn_meme', 'Add an image to the meme library.'),
|
|
44
|
-
tool('meme_list', 'List meme images.'),
|
|
45
|
-
tool('present', 'Declare existing files as final deliverables.'),
|
|
46
|
-
tool('job_list', 'List background jobs.'),
|
|
47
|
-
tool('job_output', 'Read a background job.'),
|
|
48
|
-
tool('workflow', 'Run a JavaScript workflow of subagents.'),
|
|
49
|
-
tool('subagent', 'Delegate a task to a subagent.'),
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
function makeHost({
|
|
53
|
-
defs = [...BASE, TOOL_SEARCH_SPEC],
|
|
54
|
-
config = { d6: { enabled: true } },
|
|
55
|
-
restrictThrows = false,
|
|
56
|
-
} = {}) {
|
|
57
|
-
const layers = [];
|
|
58
|
-
const infos = [];
|
|
59
|
-
const warns = [];
|
|
60
|
-
const listeners = new Map();
|
|
61
|
-
const tools = {
|
|
62
|
-
definitions() {
|
|
63
|
-
const denied = new Set(layers.flatMap((layer) => [...layer]));
|
|
64
|
-
return defs.filter((entry) => !denied.has(entry.name));
|
|
65
|
-
},
|
|
66
|
-
view() {
|
|
67
|
-
const denied = new Set(layers.flatMap((layer) => [...layer]));
|
|
68
|
-
const visible = new Map();
|
|
69
|
-
for (const entry of defs) {
|
|
70
|
-
if (!denied.has(entry.name)) visible.set(entry.name, { definition: entry });
|
|
71
|
-
}
|
|
72
|
-
return { visible, restrictableNames: new Set(defs.map((entry) => entry.name)) };
|
|
73
|
-
},
|
|
74
|
-
restrict({ deny }) {
|
|
75
|
-
if (restrictThrows) throw new Error('restrict boom');
|
|
76
|
-
const layer = new Set(deny);
|
|
77
|
-
layers.push(layer);
|
|
78
|
-
let active = true;
|
|
79
|
-
return () => {
|
|
80
|
-
if (!active) return;
|
|
81
|
-
active = false;
|
|
82
|
-
const index = layers.indexOf(layer);
|
|
83
|
-
if (index >= 0) layers.splice(index, 1);
|
|
84
|
-
};
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
const ctx = {
|
|
88
|
-
tools,
|
|
89
|
-
on(name, listener) {
|
|
90
|
-
const list = listeners.get(name) ?? [];
|
|
91
|
-
list.push(listener);
|
|
92
|
-
listeners.set(name, list);
|
|
93
|
-
return () => {
|
|
94
|
-
const index = list.indexOf(listener);
|
|
95
|
-
if (index >= 0) list.splice(index, 1);
|
|
96
|
-
};
|
|
97
|
-
},
|
|
98
|
-
logger() {
|
|
99
|
-
return {
|
|
100
|
-
info: (text) => infos.push(text),
|
|
101
|
-
warn: (text) => warns.push(text),
|
|
102
|
-
debug: () => {},
|
|
103
|
-
};
|
|
104
|
-
},
|
|
105
|
-
effect(fn) {
|
|
106
|
-
try {
|
|
107
|
-
fn();
|
|
108
|
-
} catch {
|
|
109
|
-
/* fake scope */
|
|
110
|
-
}
|
|
111
|
-
},
|
|
112
|
-
};
|
|
113
|
-
const agent = { ctx, session: { snapshotEvents: () => [] } };
|
|
114
|
-
const registration = registerD6(ctx, config);
|
|
115
|
-
const emitClaimed = (message, turn = 1) => {
|
|
116
|
-
for (const listener of listeners.get('agent/inbox/claimed') ?? []) {
|
|
117
|
-
listener({ agent, message, turn });
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
const emitAssemble = async (assembly) => {
|
|
121
|
-
let current = assembly;
|
|
122
|
-
for (const listener of listeners.get('system-prompt/assemble') ?? []) {
|
|
123
|
-
current = await listener(assembly, { agent, scope: agent }, async () => current);
|
|
124
|
-
}
|
|
125
|
-
return current;
|
|
126
|
-
};
|
|
127
|
-
return { ctx, agent, registration, tools, layers, infos, warns, emitClaimed, emitAssemble, defs };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const USER = (text) => ({ role: 'user', source: { kind: 'user' }, content: [{ type: 'text', text }] });
|
|
131
|
-
|
|
132
|
-
test('D6 is off by default: no listener, no tool, no restriction', () => {
|
|
133
|
-
const host = makeHost({ config: {} });
|
|
134
|
-
assert.equal(host.registration.enabled, false);
|
|
135
|
-
assert.deepEqual(host.registration.tools, []);
|
|
136
|
-
host.emitClaimed(USER('修复 TypeScript 编译错误'));
|
|
137
|
-
assert.equal(host.layers.length, 0);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test('code task keeps the core surface and trims media/job tools', async () => {
|
|
141
|
-
const host = makeHost();
|
|
142
|
-
host.emitClaimed(USER('修复这个 TypeScript 编译错误,补单元测试并跑通'));
|
|
143
|
-
const state = host.registration.runtime.stateFor(host.agent);
|
|
144
|
-
assert.equal(state.task, 'code');
|
|
145
|
-
assert.equal(state.frozen, true);
|
|
146
|
-
assert.equal(state.fallback, null);
|
|
147
|
-
assert.ok(state.allow.has('read'));
|
|
148
|
-
assert.ok(state.allow.has('pwsh'));
|
|
149
|
-
assert.ok(state.allow.has('session_health'));
|
|
150
|
-
assert.ok(state.allow.has('memory_recall'));
|
|
151
|
-
assert.ok(state.allow.has('tool_search'));
|
|
152
|
-
assert.ok(!state.allow.has('read_image'));
|
|
153
|
-
assert.ok(!state.allow.has('job_list'));
|
|
154
|
-
assert.equal(host.layers.length, 1);
|
|
155
|
-
const denied = new Set(host.layers.flatMap((layer) => [...layer]));
|
|
156
|
-
assert.ok(denied.has('read_image'));
|
|
157
|
-
assert.ok(denied.has('send_meme'));
|
|
158
|
-
assert.ok(!denied.has('read'));
|
|
159
|
-
|
|
160
|
-
// 0.2.2: tools are provider-owned after the waterfall; leave it untouched.
|
|
161
|
-
const assembly022 = { sections: {}, contexts: {}, variables: {} };
|
|
162
|
-
assert.equal(await host.emitAssemble(assembly022), assembly022);
|
|
163
|
-
|
|
164
|
-
// 0.2.1: the waterfall value owns tools; filter it.
|
|
165
|
-
const full = { sections: {}, contexts: {}, variables: {}, tools: host.defs };
|
|
166
|
-
const filtered = await host.emitAssemble(full);
|
|
167
|
-
const names = filtered.tools.map((entry) => entry.name);
|
|
168
|
-
assert.ok(names.includes('read'));
|
|
169
|
-
assert.ok(names.includes('tool_search'));
|
|
170
|
-
assert.ok(!names.includes('read_image'));
|
|
171
|
-
assert.ok(!names.includes('workflow'));
|
|
172
|
-
assert.equal(filtered.tools.length, names.length);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
test('media task keeps the media tools', () => {
|
|
176
|
-
const host = makeHost();
|
|
177
|
-
host.emitClaimed(USER('帮我生成一张图片做封面,再配一段音乐'));
|
|
178
|
-
const state = host.registration.runtime.stateFor(host.agent);
|
|
179
|
-
assert.equal(state.task, 'media');
|
|
180
|
-
assert.ok(state.allow.has('read_image'));
|
|
181
|
-
assert.ok(state.allow.has('send_meme'));
|
|
182
|
-
assert.ok(state.allow.has('present'));
|
|
183
|
-
assert.ok(!state.allow.has('job_list'));
|
|
184
|
-
assert.ok(!state.allow.has('workflow'));
|
|
185
|
-
const denied = new Set(host.layers.flatMap((layer) => [...layer]));
|
|
186
|
-
assert.ok(!denied.has('read_image'));
|
|
187
|
-
assert.ok(denied.has('job_list'));
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
test('the allowlist freezes after the first claimed user message', () => {
|
|
191
|
-
const host = makeHost();
|
|
192
|
-
host.emitClaimed(USER('修复这个 TypeScript bug'));
|
|
193
|
-
const state = host.registration.runtime.stateFor(host.agent);
|
|
194
|
-
const allow = [...state.allow].sort();
|
|
195
|
-
const deny = [...state.deny].sort();
|
|
196
|
-
const layers = host.layers.length;
|
|
197
|
-
|
|
198
|
-
host.emitClaimed(USER('换成视频任务,生成一张图片'), 1);
|
|
199
|
-
assert.equal(state.task, 'code');
|
|
200
|
-
assert.deepEqual([...state.allow].sort(), allow);
|
|
201
|
-
assert.deepEqual([...state.deny].sort(), deny);
|
|
202
|
-
assert.equal(host.layers.length, layers);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
test('a session resumed after a host restart freezes on its first turn>1 claim', () => {
|
|
206
|
-
// The host restarted; the restored conversation's first *new* user turn is #29.
|
|
207
|
-
// Before the G3 fix `if (turn > 1) return` skipped this claim forever.
|
|
208
|
-
const host = makeHost();
|
|
209
|
-
host.emitClaimed(USER('继续,把剩下的测试补完并跑通'), 29);
|
|
210
|
-
const state = host.registration.runtime.stateFor(host.agent);
|
|
211
|
-
assert.equal(state.frozen, true);
|
|
212
|
-
assert.equal(state.task, 'code');
|
|
213
|
-
assert.ok(state.trimmed.length > 0);
|
|
214
|
-
assert.equal(host.layers.length, 1);
|
|
215
|
-
const denied = new Set(host.layers.flatMap((layer) => [...layer]));
|
|
216
|
-
assert.ok(denied.has('read_image'));
|
|
217
|
-
assert.ok(denied.has('send_meme'));
|
|
218
|
-
assert.ok(!denied.has('read'));
|
|
219
|
-
|
|
220
|
-
const allow = [...state.allow].sort();
|
|
221
|
-
const deny = [...state.deny].sort();
|
|
222
|
-
// `tool_search` is usable right away, so the resumed session can recover tools.
|
|
223
|
-
const listed = host.registration.tools[0].invoke({}, { exec: { agent: host.agent } });
|
|
224
|
-
assert.equal(listed.ok, true);
|
|
225
|
-
assert.ok(listed.totalTrimmed > 0);
|
|
226
|
-
|
|
227
|
-
// ...and the surface stays frozen for later turns, exactly like a turn-1 session.
|
|
228
|
-
host.emitClaimed(USER('再顺手生成一张封面图'), 30);
|
|
229
|
-
assert.equal(state.task, 'code');
|
|
230
|
-
assert.deepEqual([...state.allow].sort(), allow);
|
|
231
|
-
assert.deepEqual([...state.deny].sort(), deny);
|
|
232
|
-
assert.equal(host.layers.length, 1);
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
test('a turn>1 claim that is not a real user message still does not freeze', () => {
|
|
236
|
-
const host = makeHost();
|
|
237
|
-
host.emitClaimed({ role: 'assistant', content: [{ type: 'text', text: '工具输出' }] }, 29);
|
|
238
|
-
const state = host.registration.runtime.stateFor(host.agent);
|
|
239
|
-
assert.equal(state.frozen, false);
|
|
240
|
-
assert.equal(host.layers.length, 0);
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
test('tool_search lifts one restriction and 0.2.1 appends it at the END', async () => {
|
|
244
|
-
const host = makeHost();
|
|
245
|
-
host.emitClaimed(USER('修复这个 TypeScript bug'));
|
|
246
|
-
const before = (await host.emitAssemble({ sections: {}, contexts: {}, variables: {}, tools: host.defs })).tools
|
|
247
|
-
.map((entry) => entry.name);
|
|
248
|
-
|
|
249
|
-
assert.equal(typeof host.registration.tools[0].invoke, 'function');
|
|
250
|
-
assert.equal(host.registration.tools[0].execute, undefined);
|
|
251
|
-
|
|
252
|
-
const result = host.registration.tools[0].invoke(
|
|
253
|
-
{ names: ['read_image'] },
|
|
254
|
-
{ exec: { agent: host.agent }, host: () => host.ctx },
|
|
255
|
-
);
|
|
256
|
-
assert.equal(result.ok, true);
|
|
257
|
-
assert.deepEqual(result.added, ['read_image']);
|
|
258
|
-
const denied = new Set(host.layers.flatMap((layer) => [...layer]));
|
|
259
|
-
assert.ok(!denied.has('read_image'));
|
|
260
|
-
assert.ok(denied.has('send_meme'));
|
|
261
|
-
|
|
262
|
-
const after = (await host.emitAssemble({ sections: {}, contexts: {}, variables: {}, tools: host.defs })).tools
|
|
263
|
-
.map((entry) => entry.name);
|
|
264
|
-
assert.equal(after.at(-1), 'read_image');
|
|
265
|
-
assert.deepEqual(after.slice(0, before.length), before);
|
|
266
|
-
assert.ok(!after.slice(0, before.length).includes('read_image'));
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
test('tool_search lists trimmed tools and filters by query', () => {
|
|
270
|
-
const host = makeHost();
|
|
271
|
-
host.emitClaimed(USER('修复这个 TypeScript bug'));
|
|
272
|
-
const listed = host.registration.tools[0].invoke({}, { exec: { agent: host.agent } });
|
|
273
|
-
assert.equal(listed.ok, true);
|
|
274
|
-
assert.equal(listed.totalTrimmed, host.registration.runtime.stateFor(host.agent).trimmed.length);
|
|
275
|
-
assert.ok(listed.matched.length <= 8);
|
|
276
|
-
const queried = host.registration.tools[0].invoke(
|
|
277
|
-
{ query: 'meme', names: [] },
|
|
278
|
-
{ exec: { agent: host.agent } },
|
|
279
|
-
);
|
|
280
|
-
assert.equal(queried.ok, true);
|
|
281
|
-
assert.ok(queried.matched.some((entry) => entry.name === 'send_meme'));
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
test('a thrown restrict() falls back to the full surface', async () => {
|
|
285
|
-
const host = makeHost({ restrictThrows: true });
|
|
286
|
-
host.emitClaimed(USER('修复这个 TypeScript bug'));
|
|
287
|
-
const state = host.registration.runtime.stateFor(host.agent);
|
|
288
|
-
assert.equal(state.fallback, 'full');
|
|
289
|
-
assert.equal(state.applied, false);
|
|
290
|
-
assert.equal(host.layers.length, 0);
|
|
291
|
-
const full = { sections: {}, contexts: {}, variables: {}, tools: host.defs };
|
|
292
|
-
const result = await host.emitAssemble(full);
|
|
293
|
-
assert.equal(result.tools.length, full.tools.length);
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
test('an empty surviving set falls back to the full surface', async () => {
|
|
297
|
-
const host = makeHost({
|
|
298
|
-
defs: [tool('run_code', 'Reserved tool.'), tool('send_meme', 'Send a meme image.')],
|
|
299
|
-
});
|
|
300
|
-
host.emitClaimed(USER('修复这个 TypeScript bug'));
|
|
301
|
-
const state = host.registration.runtime.stateFor(host.agent);
|
|
302
|
-
assert.equal(state.fallback, 'full');
|
|
303
|
-
assert.equal(host.layers.length, 0);
|
|
304
|
-
const full = { sections: {}, contexts: {}, variables: {}, tools: host.defs };
|
|
305
|
-
const result = await host.emitAssemble(full);
|
|
306
|
-
assert.equal(result.tools.length, 2);
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
test('the live goal participates in the task class', () => {
|
|
310
|
-
const host = makeHost();
|
|
311
|
-
host.agent.ctx.goals = { get: () => ({ objective: '生成一段音乐视频并配封面', phase: 'active' }) };
|
|
312
|
-
host.emitClaimed(USER('按这个目标开始'));
|
|
313
|
-
assert.equal(host.registration.runtime.stateFor(host.agent).task, 'media');
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
test('dry-run logs the trimmed names but never restricts or adds tool_search', async () => {
|
|
317
|
-
const host = makeHost({ config: { d6: { enabled: true, dryRun: true } } });
|
|
318
|
-
host.emitClaimed(USER('修复这个 TypeScript bug'));
|
|
319
|
-
const state = host.registration.runtime.stateFor(host.agent);
|
|
320
|
-
assert.equal(state.frozen, true);
|
|
321
|
-
assert.equal(state.applied, false);
|
|
322
|
-
assert.deepEqual(host.registration.tools, []);
|
|
323
|
-
assert.equal(host.layers.length, 0);
|
|
324
|
-
assert.ok(host.infos.some((line) => line.includes('[d6] dry-run task=code')));
|
|
325
|
-
assert.ok(host.infos.some((line) => line.includes('read_image')));
|
|
326
|
-
const full = { sections: {}, contexts: {}, variables: {}, tools: host.defs };
|
|
327
|
-
assert.equal((await host.emitAssemble(full)).tools.length, host.defs.length);
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
test('both assemble shapes are detected and handled', async () => {
|
|
331
|
-
assert.equal(assembleShape({ tools: [] }), '0.2.1');
|
|
332
|
-
assert.equal(assembleShape({ sections: {} }), '0.2.2');
|
|
333
|
-
|
|
334
|
-
const host022 = makeHost();
|
|
335
|
-
host022.emitClaimed(USER('修复这个 TypeScript bug'));
|
|
336
|
-
const assembly022 = { sections: {} };
|
|
337
|
-
assert.equal(await host022.emitAssemble(assembly022), assembly022);
|
|
338
|
-
assert.equal(host022.registration.runtime.stateFor(host022.agent).shape, '0.2.2');
|
|
339
|
-
|
|
340
|
-
const host021 = makeHost();
|
|
341
|
-
host021.emitClaimed(USER('修复这个 TypeScript bug'));
|
|
342
|
-
const assembly021 = { sections: {}, tools: host021.defs };
|
|
343
|
-
const result = await host021.emitAssemble(assembly021);
|
|
344
|
-
assert.ok(result.tools.length < host021.defs.length);
|
|
345
|
-
assert.equal(host021.registration.runtime.stateFor(host021.agent).shape, '0.2.1');
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
test('budget: the code-task surface is <= 10K tokens (real 108-tool header)', () => {
|
|
349
|
-
const all = [...REAL_TOOLS, TOOL_SEARCH_SPEC];
|
|
350
|
-
const fullTokens = estimateToolsTokens(all);
|
|
351
|
-
const selection = selectAllowlist({
|
|
352
|
-
message: '修复这个 TypeScript 编译错误,补单元测试并跑通',
|
|
353
|
-
tools: all,
|
|
354
|
-
});
|
|
355
|
-
assert.equal(selection.task, 'code');
|
|
356
|
-
const kept = all.filter((entry) => selection.allow.includes(entry.name));
|
|
357
|
-
const keptTokens = estimateToolsTokens(kept);
|
|
358
|
-
assert.equal(kept.length, 29);
|
|
359
|
-
assert.ok(fullTokens > 25000, `full surface should exceed 25K, got ${fullTokens}`);
|
|
360
|
-
assert.ok(keptTokens <= 10000, `code surface must be <= 10K, got ${keptTokens}`);
|
|
361
|
-
assert.ok(kept.some((entry) => entry.name === 'tool_search'));
|
|
362
|
-
assert.ok(!kept.some((entry) => entry.name === 'read_image'));
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
test('apply() wires tool_search and both listeners only when d6.enabled=true', async () => {
|
|
366
|
-
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'longlive-d6-'));
|
|
367
|
-
const home = path.join(temp, 'home');
|
|
368
|
-
const memoryRoot = path.join(temp, 'memory');
|
|
369
|
-
fs.mkdirSync(home, { recursive: true });
|
|
370
|
-
fs.mkdirSync(memoryRoot, { recursive: true });
|
|
371
|
-
const tools = [];
|
|
372
|
-
const hooks = [];
|
|
373
|
-
const ctx = {
|
|
374
|
-
tools: { register: (definition) => { tools.push(definition); return () => {}; } },
|
|
375
|
-
on(name, listener) { hooks.push({ name, listener }); return () => {}; },
|
|
376
|
-
logger() { return { warn: () => {}, info: () => {}, debug: () => {} }; },
|
|
377
|
-
effect(fn) { const dispose = fn(); return typeof dispose === 'function' ? dispose : () => {}; },
|
|
378
|
-
};
|
|
379
|
-
const count = await apply(ctx, {
|
|
380
|
-
d6: { enabled: true },
|
|
381
|
-
home,
|
|
382
|
-
memoryRoot,
|
|
383
|
-
defineTool: (definition) => definition,
|
|
384
|
-
routerEnabled: false,
|
|
385
|
-
});
|
|
386
|
-
assert.ok(count >= 23);
|
|
387
|
-
assert.ok(tools.some((definition) => definition.name === 'tool_search'));
|
|
388
|
-
const search = tools.find((definition) => definition.name === 'tool_search');
|
|
389
|
-
assert.equal(typeof search.execute, 'function');
|
|
390
|
-
const unfrozen = await search.execute({}, { agent: {} });
|
|
391
|
-
assert.equal(unfrozen.error, 'D6_NOT_FROZEN');
|
|
392
|
-
assert.ok(hooks.some((hook) => hook.name === 'agent/inbox/claimed'));
|
|
393
|
-
assert.ok(hooks.some((hook) => hook.name === 'system-prompt/assemble'));
|
|
394
|
-
fs.rmSync(temp, { recursive: true, force: true });
|
|
395
|
-
});
|