@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/src/activity/index.js
CHANGED
|
@@ -31,12 +31,14 @@ import {
|
|
|
31
31
|
textOfMessage,
|
|
32
32
|
} from '../identity/inject-position.js';
|
|
33
33
|
import { privacyScan as defaultPrivacyScan } from '../recall/privacy.mjs';
|
|
34
|
+
import { startPrestepPhase, notePrestepDegrade } from '../metrics/prestep-latency.mjs';
|
|
34
35
|
import {
|
|
35
36
|
ACTIVITY_HINT_MARK,
|
|
36
37
|
ACTIVITY_REFRESH_THROTTLE_MS,
|
|
37
38
|
buildActivityKey,
|
|
38
39
|
buildActivityText,
|
|
39
40
|
buildToolActivityText,
|
|
41
|
+
charLength,
|
|
40
42
|
classifyKind,
|
|
41
43
|
clip,
|
|
42
44
|
oneLine,
|
|
@@ -44,6 +46,14 @@ import {
|
|
|
44
46
|
selectActivities,
|
|
45
47
|
shortSessionId,
|
|
46
48
|
} from './model.js';
|
|
49
|
+
import {
|
|
50
|
+
THINKING_HINT_MARK,
|
|
51
|
+
extractThinkingText,
|
|
52
|
+
isThinkingPayloadSafe,
|
|
53
|
+
renderThinkingHint,
|
|
54
|
+
selectThinkingActivities,
|
|
55
|
+
summarizeThinking,
|
|
56
|
+
} from './thinking.js';
|
|
47
57
|
import {
|
|
48
58
|
ACTIVITY_SESSION_RE,
|
|
49
59
|
activityBoardFile,
|
|
@@ -56,7 +66,9 @@ export const ACTIVITY_STATE_VERSION = 1;
|
|
|
56
66
|
export const ACTIVITY_STATE_DIR = path.join('.longlive', 'bridge');
|
|
57
67
|
|
|
58
68
|
export const ACTIVITY_DEFAULTS = Object.freeze({
|
|
59
|
-
|
|
69
|
+
// 1.8.0 product default: real-time shared memory is ON by default; users opt
|
|
70
|
+
// out with an explicit `enabled: false` (never by omission).
|
|
71
|
+
enabled: true,
|
|
60
72
|
injectMaxChars: 300,
|
|
61
73
|
maxItems: 5,
|
|
62
74
|
});
|
|
@@ -71,7 +83,8 @@ function clampInt(value, fallback, min, max) {
|
|
|
71
83
|
export function normalizeActivityConfig(input = {}) {
|
|
72
84
|
const raw = input !== null && typeof input === 'object' ? input : {};
|
|
73
85
|
return {
|
|
74
|
-
|
|
86
|
+
// Default ON (1.8.0): only a literal false opts out.
|
|
87
|
+
enabled: raw.enabled !== false,
|
|
75
88
|
injectMaxChars: clampInt(raw.injectMaxChars, ACTIVITY_DEFAULTS.injectMaxChars, 60, 2000),
|
|
76
89
|
maxItems: clampInt(raw.maxItems, ACTIVITY_DEFAULTS.maxItems, 1, 10),
|
|
77
90
|
createUserMessage: typeof raw.createUserMessage === 'function' ? raw.createUserMessage : undefined,
|
|
@@ -212,6 +225,9 @@ async function resolveCreateUserMessage(explicit) {
|
|
|
212
225
|
*/
|
|
213
226
|
export function createActivityStream(options = {}) {
|
|
214
227
|
const cfg = normalizeActivityConfig(options);
|
|
228
|
+
// 1.8.0: thinking summaries are an independent, default-off injection switch.
|
|
229
|
+
// The write side also requires it, so off is byte-for-byte the 1.7.4 behaviour.
|
|
230
|
+
const thinkingInjection = options.thinkingInjection === true;
|
|
215
231
|
const memoryRoot = typeof options.memoryRoot === 'string' && options.memoryRoot.trim() !== ''
|
|
216
232
|
? path.resolve(options.memoryRoot)
|
|
217
233
|
: null;
|
|
@@ -231,6 +247,8 @@ export function createActivityStream(options = {}) {
|
|
|
231
247
|
privacy: 0,
|
|
232
248
|
resets: 0,
|
|
233
249
|
errors: 0,
|
|
250
|
+
thinkingWritten: 0,
|
|
251
|
+
thinkingInjected: 0,
|
|
234
252
|
};
|
|
235
253
|
const sessions = new Map();
|
|
236
254
|
// `${sessionId}|${key}` → { at: ms, kind } — 同 key 节流 ≤1 次刷新/10s.
|
|
@@ -249,6 +267,8 @@ export function createActivityStream(options = {}) {
|
|
|
249
267
|
assistantText: '',
|
|
250
268
|
tool: null,
|
|
251
269
|
toolCalls: 0,
|
|
270
|
+
thinkingTurn: null,
|
|
271
|
+
thinkingWritten: false,
|
|
252
272
|
};
|
|
253
273
|
sessions.set(sessionId, state);
|
|
254
274
|
}
|
|
@@ -386,6 +406,8 @@ export function createActivityStream(options = {}) {
|
|
|
386
406
|
state.assistantText = '';
|
|
387
407
|
state.tool = null;
|
|
388
408
|
state.toolCalls = 0;
|
|
409
|
+
state.thinkingTurn = null;
|
|
410
|
+
state.thinkingWritten = false;
|
|
389
411
|
// A new turn is new information: do not let the previous turn's 10s
|
|
390
412
|
// window swallow its first tool action.
|
|
391
413
|
clearSessionRefreshes(sessionId);
|
|
@@ -405,6 +427,40 @@ export function createActivityStream(options = {}) {
|
|
|
405
427
|
if (Number.isInteger(turn) && Number.isInteger(state.turn) && turn !== state.turn) return;
|
|
406
428
|
const text = messageTextOf(event);
|
|
407
429
|
if (text !== '') state.assistantText = text;
|
|
430
|
+
// 1.8.0 thinking summaries: at most one row per session+turn; only when
|
|
431
|
+
// the injection switch is on. Visible reasoning text exists or nothing
|
|
432
|
+
// is written (no model call, no raw log rewrite).
|
|
433
|
+
if (thinkingInjection) {
|
|
434
|
+
const resolvedTurn = Number.isInteger(turn) ? turn : (Number.isInteger(state.turn) ? state.turn : null);
|
|
435
|
+
const alreadyWritten = state.thinkingWritten
|
|
436
|
+
&& (state.thinkingTurn === resolvedTurn || resolvedTurn === null);
|
|
437
|
+
if (!alreadyWritten) {
|
|
438
|
+
const summary = summarizeThinking(extractThinkingText(data.message?.content));
|
|
439
|
+
if (summary !== '') {
|
|
440
|
+
const result = store.append(
|
|
441
|
+
{
|
|
442
|
+
sessionId,
|
|
443
|
+
kind: 'thinking',
|
|
444
|
+
text: summary,
|
|
445
|
+
key: `thinking|${resolvedTurn ?? 'turn'}`,
|
|
446
|
+
at: now(),
|
|
447
|
+
turnRef: resolvedTurn,
|
|
448
|
+
charCount: charLength(summary),
|
|
449
|
+
},
|
|
450
|
+
{ touchSeq: true, refreshText: false },
|
|
451
|
+
);
|
|
452
|
+
if (result.ok) {
|
|
453
|
+
state.thinkingTurn = resolvedTurn;
|
|
454
|
+
state.thinkingWritten = true;
|
|
455
|
+
stats.thinkingWritten += 1;
|
|
456
|
+
} else if (result.code === 'E_PRIVACY') {
|
|
457
|
+
stats.privacy += 1;
|
|
458
|
+
} else {
|
|
459
|
+
stats.errors += 1;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
408
464
|
return;
|
|
409
465
|
}
|
|
410
466
|
|
|
@@ -473,7 +529,10 @@ export function createActivityStream(options = {}) {
|
|
|
473
529
|
const messages = payload?.messages;
|
|
474
530
|
if (!Array.isArray(messages)) return skip('no-messages');
|
|
475
531
|
if (!messages.some((message) => isHumanMessage(message))) return skip('empty-turn');
|
|
476
|
-
if (messages.some((message) =>
|
|
532
|
+
if (messages.some((message) => {
|
|
533
|
+
const text = textOfMessage(message);
|
|
534
|
+
return text.includes(ACTIVITY_HINT_MARK) || text.includes(THINKING_HINT_MARK);
|
|
535
|
+
})) {
|
|
477
536
|
return { injected: false, skipped: 'turn-already-injected' };
|
|
478
537
|
}
|
|
479
538
|
|
|
@@ -494,7 +553,18 @@ export function createActivityStream(options = {}) {
|
|
|
494
553
|
lastSeenSeq: effectiveSeen,
|
|
495
554
|
maxItems: cfg.maxItems,
|
|
496
555
|
});
|
|
497
|
-
|
|
556
|
+
// 1.8.0: thinking summaries are selected separately and never mixed into
|
|
557
|
+
// the normal activity lines (the cursor is the same lastSeenSeq).
|
|
558
|
+
const freshThinking = thinkingInjection
|
|
559
|
+
? selectThinkingActivities(read.entries, {
|
|
560
|
+
selfSessionId: sessionId,
|
|
561
|
+
lastSeenSeq: effectiveSeen,
|
|
562
|
+
maxItems: 5,
|
|
563
|
+
})
|
|
564
|
+
: [];
|
|
565
|
+
if (fresh.length === 0 && freshThinking.length === 0) {
|
|
566
|
+
return { injected: false, skipped: 'no-new', reset: resetCursor };
|
|
567
|
+
}
|
|
498
568
|
|
|
499
569
|
const allowed = [];
|
|
500
570
|
for (const item of fresh) {
|
|
@@ -510,14 +580,44 @@ export function createActivityStream(options = {}) {
|
|
|
510
580
|
}
|
|
511
581
|
allowed.push(item);
|
|
512
582
|
}
|
|
513
|
-
|
|
583
|
+
const allowedThinking = [];
|
|
584
|
+
for (const item of freshThinking) {
|
|
585
|
+
const summary = summarizeThinking(item.text);
|
|
586
|
+
if (summary === '' || !isThinkingPayloadSafe(summary)) {
|
|
587
|
+
stats.privacy += 1;
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
try {
|
|
591
|
+
const verdict = privacyScan(summary);
|
|
592
|
+
if (!verdict || verdict.verdict === 'local_only') {
|
|
593
|
+
stats.privacy += 1;
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
} catch {
|
|
597
|
+
stats.privacy += 1;
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
allowedThinking.push({ ...item, text: summary });
|
|
601
|
+
}
|
|
602
|
+
if (allowed.length === 0 && allowedThinking.length === 0) {
|
|
603
|
+
return { injected: false, skipped: 'privacy' };
|
|
604
|
+
}
|
|
514
605
|
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
606
|
+
const parts = [];
|
|
607
|
+
if (allowed.length > 0) {
|
|
608
|
+
const rendered = renderActivityHint(allowed, {
|
|
609
|
+
maxChars: cfg.injectMaxChars,
|
|
610
|
+
maxItems: cfg.maxItems,
|
|
611
|
+
nowMs: now().getTime(),
|
|
612
|
+
});
|
|
613
|
+
if (rendered.text !== '') parts.push(rendered.text);
|
|
614
|
+
}
|
|
615
|
+
if (allowedThinking.length > 0) {
|
|
616
|
+
const thinkingText = renderThinkingHint(allowedThinking, { maxItems: 5 });
|
|
617
|
+
if (thinkingText !== '') parts.push(thinkingText);
|
|
618
|
+
}
|
|
619
|
+
if (parts.length === 0) return skip('budget-empty');
|
|
620
|
+
const text = parts.join('\n\n');
|
|
521
621
|
|
|
522
622
|
const createUserMessage = await resolveCreateUserMessage(cfg.createUserMessage);
|
|
523
623
|
if (typeof createUserMessage !== 'function') return skip('no-create-user-message');
|
|
@@ -525,7 +625,7 @@ export function createActivityStream(options = {}) {
|
|
|
525
625
|
try {
|
|
526
626
|
message = assertInjectionSource(createUserMessage({
|
|
527
627
|
role: 'user',
|
|
528
|
-
content: [{ type: 'text', text
|
|
628
|
+
content: [{ type: 'text', text }],
|
|
529
629
|
source: injectionSource(),
|
|
530
630
|
}));
|
|
531
631
|
} catch {
|
|
@@ -539,10 +639,11 @@ export function createActivityStream(options = {}) {
|
|
|
539
639
|
return skip('splice-failed');
|
|
540
640
|
}
|
|
541
641
|
|
|
642
|
+
const injectedItems = [...allowed, ...allowedThinking];
|
|
542
643
|
const nextState = {
|
|
543
644
|
...state,
|
|
544
645
|
lastSeenSeq: Math.max(effectiveSeen, maxSeqOf(read.entries)),
|
|
545
|
-
lastInjectedSeq: maxSeqOf(
|
|
646
|
+
lastInjectedSeq: maxSeqOf(injectedItems),
|
|
546
647
|
lastInjectedAt: now().toISOString(),
|
|
547
648
|
injectedTurns: state.injectedTurns + 1,
|
|
548
649
|
resets: state.resets + (resetCursor ? 1 : 0),
|
|
@@ -555,13 +656,15 @@ export function createActivityStream(options = {}) {
|
|
|
555
656
|
/* state is an optimization; a failed write costs a repeat, not a turn */
|
|
556
657
|
}
|
|
557
658
|
stats.injected += 1;
|
|
659
|
+
stats.thinkingInjected += allowedThinking.length;
|
|
558
660
|
return {
|
|
559
661
|
injected: true,
|
|
560
662
|
sessionId,
|
|
561
|
-
chars:
|
|
663
|
+
chars: charLength(text),
|
|
562
664
|
reset: resetCursor,
|
|
563
|
-
items:
|
|
564
|
-
|
|
665
|
+
items: injectedItems.map((item) => ({ seq: item.seq, sessionId: item.sessionId, kind: item.kind })),
|
|
666
|
+
thinkingItems: allowedThinking.length,
|
|
667
|
+
text,
|
|
565
668
|
};
|
|
566
669
|
}
|
|
567
670
|
|
|
@@ -576,10 +679,16 @@ export function createActivityStream(options = {}) {
|
|
|
576
679
|
}
|
|
577
680
|
try {
|
|
578
681
|
const dispose = ctx.on('agent/pre-step', async (payload, next) => {
|
|
682
|
+
// B3: shared phase = shared-brain injection only (timing only).
|
|
683
|
+
const sessionId = preStepSessionId(payload);
|
|
684
|
+
const phase = startPrestepPhase(sessionId, payload?.turn, 'shared');
|
|
579
685
|
try {
|
|
580
686
|
await maybeInject(payload);
|
|
581
687
|
} catch {
|
|
582
688
|
stats.errors += 1;
|
|
689
|
+
notePrestepDegrade(sessionId, payload?.turn, { kind: 'error', phase: 'shared', detail: 'E_ACTIVITY_PRE_STEP' });
|
|
690
|
+
} finally {
|
|
691
|
+
phase.end();
|
|
583
692
|
}
|
|
584
693
|
return typeof next === 'function' ? next() : undefined;
|
|
585
694
|
});
|
|
@@ -607,7 +716,7 @@ export function createActivityStream(options = {}) {
|
|
|
607
716
|
return {
|
|
608
717
|
enabled,
|
|
609
718
|
memoryRoot,
|
|
610
|
-
config: cfg,
|
|
719
|
+
config: { ...cfg, thinkingInjection },
|
|
611
720
|
stats,
|
|
612
721
|
store,
|
|
613
722
|
install,
|
|
@@ -625,10 +734,11 @@ export function createActivityStream(options = {}) {
|
|
|
625
734
|
*/
|
|
626
735
|
export function registerActivityStream(ctx, cfg = {}, options = {}) {
|
|
627
736
|
const stream = createActivityStream({
|
|
628
|
-
enabled: cfg.enabled
|
|
737
|
+
enabled: cfg.enabled !== false,
|
|
629
738
|
memoryRoot: options.memoryRoot,
|
|
630
739
|
injectMaxChars: cfg.injectMaxChars,
|
|
631
740
|
maxItems: cfg.maxItems,
|
|
741
|
+
thinkingInjection: cfg.thinkingInjection === true,
|
|
632
742
|
createUserMessage: cfg.createUserMessage,
|
|
633
743
|
now: options.now,
|
|
634
744
|
privacyScan: options.privacyScan,
|
|
@@ -647,3 +757,4 @@ export function registerActivityStream(ctx, cfg = {}, options = {}) {
|
|
|
647
757
|
}
|
|
648
758
|
|
|
649
759
|
export { ACTIVITY_HINT_MARK, ACTIVITY_REFRESH_THROTTLE_MS, shortSessionId, activityBoardFile };
|
|
760
|
+
export { THINKING_HINT_MARK, THINKING_KIND, THINKING_LINE_MAX, THINKING_MAX_ITEMS } from './thinking.js';
|
package/src/activity/model.js
CHANGED
|
@@ -9,10 +9,12 @@
|
|
|
9
9
|
import { classifyConclusionText } from '../conclusion.js';
|
|
10
10
|
|
|
11
11
|
/** One activity row's kind. `doing` is the default, `blocked` floats in hints. */
|
|
12
|
-
export const ACTIVITY_KINDS = Object.freeze(['doing', 'finding', 'blocked', 'done']);
|
|
12
|
+
export const ACTIVITY_KINDS = Object.freeze(['doing', 'finding', 'blocked', 'done', 'thinking']);
|
|
13
13
|
|
|
14
14
|
/** The published cap for one row's text (code points, not bytes). */
|
|
15
15
|
export const ACTIVITY_TEXT_MAX = 140;
|
|
16
|
+
/** Thinking summaries use their own published cap (one line, ≤200). */
|
|
17
|
+
export const ACTIVITY_THINKING_TEXT_MAX = 200;
|
|
16
18
|
/**
|
|
17
19
|
* Event-level rows (one per tool call) refresh in place instead of appending a
|
|
18
20
|
* new row every time. 同 key 节流 ≤1 条/10s: a refresh inside the window is a
|
|
@@ -277,11 +279,14 @@ function clampInt(value, fallback, min, max) {
|
|
|
277
279
|
/**
|
|
278
280
|
* Pick the incremental rows a session has not seen yet. Ordering: blocked
|
|
279
281
|
* first, then newest first. Rows from the consumer itself never come back.
|
|
282
|
+
* `thinking` rows are a separate section (thinking.js); they never leak into
|
|
283
|
+
* this normal activity hint unless the caller explicitly asks for that kind.
|
|
280
284
|
*/
|
|
281
285
|
export function selectActivities(entries, options = {}) {
|
|
282
286
|
const selfSessionId = typeof options.selfSessionId === 'string' ? options.selfSessionId : null;
|
|
283
287
|
const lastSeenSeq = Number.isInteger(options.lastSeenSeq) ? options.lastSeenSeq : 0;
|
|
284
288
|
const maxItems = clampInt(options.maxItems, 5, 1, 10);
|
|
289
|
+
const kinds = Array.isArray(options.kinds) ? new Set(options.kinds) : null;
|
|
285
290
|
const rows = [];
|
|
286
291
|
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
287
292
|
if (!entry || typeof entry !== 'object') continue;
|
|
@@ -289,6 +294,11 @@ export function selectActivities(entries, options = {}) {
|
|
|
289
294
|
if (selfSessionId !== null && entry.sessionId === selfSessionId) continue;
|
|
290
295
|
if (entry.archived === true) continue;
|
|
291
296
|
if (!ACTIVITY_KINDS.includes(entry.kind)) continue;
|
|
297
|
+
if (kinds !== null) {
|
|
298
|
+
if (!kinds.has(entry.kind)) continue;
|
|
299
|
+
} else if (entry.kind === 'thinking') {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
292
302
|
if (typeof entry.text !== 'string' || entry.text.trim() === '') continue;
|
|
293
303
|
rows.push(entry);
|
|
294
304
|
}
|
package/src/activity/store.js
CHANGED
|
@@ -22,10 +22,12 @@ import { privacyScan as defaultPrivacyScan } from '../recall/privacy.mjs';
|
|
|
22
22
|
import {
|
|
23
23
|
ACTIVITY_KINDS,
|
|
24
24
|
ACTIVITY_TEXT_MAX,
|
|
25
|
+
ACTIVITY_THINKING_TEXT_MAX,
|
|
25
26
|
DEDUPE_WINDOW,
|
|
26
27
|
DISTILL_MIN_COUNT,
|
|
27
28
|
DISTILL_TOTAL_COUNT,
|
|
28
29
|
SIMILARITY_THRESHOLD,
|
|
30
|
+
charLength,
|
|
29
31
|
clip,
|
|
30
32
|
distillActivityText,
|
|
31
33
|
normalizeKey,
|
|
@@ -338,12 +340,17 @@ export function selectDedupeTarget(entries, candidate, options = {}) {
|
|
|
338
340
|
const sessionId = String(candidate?.sessionId ?? '');
|
|
339
341
|
const key = normalizeKey(candidate?.key);
|
|
340
342
|
const text = oneLine(candidate?.text);
|
|
343
|
+
const candidateKind = ACTIVITY_KINDS.includes(candidate?.kind) ? candidate.kind : 'doing';
|
|
344
|
+
const thinking = candidateKind === 'thinking';
|
|
341
345
|
const rows = (Array.isArray(entries) ? entries : []).filter(
|
|
342
346
|
(entry) => entry && entry.sessionId === sessionId && entry.archived !== true,
|
|
343
347
|
);
|
|
344
348
|
const windowRows = rows.slice(-Math.max(1, windowSize));
|
|
345
349
|
let best = null;
|
|
346
350
|
for (const row of windowRows) {
|
|
351
|
+
// 1.8.0: thinking summaries and normal activity rows never merge into each
|
|
352
|
+
// other (different kind = different panel section and different cursors).
|
|
353
|
+
if ((row.kind === 'thinking') !== thinking) continue;
|
|
347
354
|
if (normalizeKey(row.key) === key) return { row, reason: 'key', score: 1 };
|
|
348
355
|
const score = similarity(row.text, text);
|
|
349
356
|
if (score >= threshold && (best === null || score > best.score)) best = { row, reason: 'similarity', score };
|
|
@@ -475,9 +482,10 @@ export function createActivityStore(options = {}) {
|
|
|
475
482
|
const refreshText = options.refreshText === true;
|
|
476
483
|
const sessionId = String(input.sessionId ?? '').trim().toLowerCase();
|
|
477
484
|
if (!ACTIVITY_SESSION_RE.test(sessionId)) return { ok: false, code: 'E_SESSION_ID' };
|
|
478
|
-
const text = clip(oneLine(input.text), ACTIVITY_TEXT_MAX);
|
|
479
|
-
if (text === '') return { ok: false, code: 'E_EMPTY_TEXT' };
|
|
480
485
|
const kind = ACTIVITY_KINDS.includes(input.kind) ? input.kind : 'doing';
|
|
486
|
+
const textMax = kind === 'thinking' ? ACTIVITY_THINKING_TEXT_MAX : ACTIVITY_TEXT_MAX;
|
|
487
|
+
const text = clip(oneLine(input.text), textMax);
|
|
488
|
+
if (text === '') return { ok: false, code: 'E_EMPTY_TEXT' };
|
|
481
489
|
const key = normalizeKey(input.key);
|
|
482
490
|
const atDate = new Date(input.at ?? now());
|
|
483
491
|
const at = Number.isFinite(atDate.getTime()) ? atDate.toISOString() : new Date().toISOString();
|
|
@@ -515,7 +523,7 @@ export function createActivityStore(options = {}) {
|
|
|
515
523
|
};
|
|
516
524
|
}
|
|
517
525
|
}
|
|
518
|
-
const target = selectDedupeTarget(entries, { sessionId, key, text });
|
|
526
|
+
const target = selectDedupeTarget(entries, { sessionId, key, text, kind });
|
|
519
527
|
let action = 'appended';
|
|
520
528
|
let entry = null;
|
|
521
529
|
let mergedSeq = null;
|
|
@@ -531,6 +539,10 @@ export function createActivityStore(options = {}) {
|
|
|
531
539
|
// generated compression, so it is refreshed with the newest occurrence:
|
|
532
540
|
// 「阶段:…(N 次,最近 …)」 must show the current N and detail.
|
|
533
541
|
row.kind = kind;
|
|
542
|
+
if (kind === 'thinking') {
|
|
543
|
+
if (Number.isInteger(input.turnRef)) row.turnRef = input.turnRef;
|
|
544
|
+
row.charCount = Number.isInteger(input.charCount) ? input.charCount : charLength(text);
|
|
545
|
+
}
|
|
534
546
|
if (row.phase === true) {
|
|
535
547
|
row.last_text = text;
|
|
536
548
|
row.text = distillActivityText(row.key, row.count, text);
|
|
@@ -571,6 +583,13 @@ export function createActivityStore(options = {}) {
|
|
|
571
583
|
dupOf: null,
|
|
572
584
|
count: 1,
|
|
573
585
|
};
|
|
586
|
+
// Thinking summaries carry their source turn and size for machine
|
|
587
|
+
// consumers (the injection line shows 「第 N 轮」); normal rows omit them.
|
|
588
|
+
if (kind === 'thinking') {
|
|
589
|
+
if (Number.isInteger(input.turnRef)) entry.turnRef = input.turnRef;
|
|
590
|
+
if (Number.isInteger(input.charCount)) entry.charCount = input.charCount;
|
|
591
|
+
else entry.charCount = charLength(text);
|
|
592
|
+
}
|
|
574
593
|
entries.push(entry);
|
|
575
594
|
action = 'appended';
|
|
576
595
|
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// activity/thinking.js — shared-brain thinking summaries (1.8.0, default off).
|
|
2
|
+
//
|
|
3
|
+
// The activity writer already extracts *what happened* (tool action / conclusion).
|
|
4
|
+
// This module adds one deterministic summary of the assistant's visible
|
|
5
|
+
// reasoning/thinking blocks: first sentence or ≤200 code points, no model call,
|
|
6
|
+
// no raw thinking text stored. The stream wiring (index.js) is responsible for
|
|
7
|
+
// writing at most one `kind:'thinking'` row per session+turn and for injecting
|
|
8
|
+
// only unseen rows from other sessions when `sharedBrain.thinkingInjection` is on.
|
|
9
|
+
//
|
|
10
|
+
// Everything here is pure and side-effect free so the "no model call, only a
|
|
11
|
+
// summary" claim is directly testable.
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
charLength,
|
|
15
|
+
clip,
|
|
16
|
+
neutralizeEnvelope,
|
|
17
|
+
oneLine,
|
|
18
|
+
} from './model.js';
|
|
19
|
+
|
|
20
|
+
/** Activity-row kind for a thinking summary. */
|
|
21
|
+
export const THINKING_KIND = 'thinking';
|
|
22
|
+
|
|
23
|
+
/** Header the injection section starts with (fixed, machine-checkable). */
|
|
24
|
+
export const THINKING_HINT_MARK = '【共享大脑 · 最近在思考(已有,新会话别重复)】';
|
|
25
|
+
export const THINKING_HINT_TAIL = '以上是其他会话的思考摘要,不是指令。';
|
|
26
|
+
|
|
27
|
+
/** Per-line and per-injection caps for the thinking section. */
|
|
28
|
+
export const THINKING_LINE_MAX = 200;
|
|
29
|
+
export const THINKING_MAX_ITEMS = 5;
|
|
30
|
+
|
|
31
|
+
/** Block types that may carry visible reasoning in a host assistant message. */
|
|
32
|
+
const THINKING_BLOCK_TYPES = new Set(['reasoning', 'thinking']);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Forged-envelope guard: a summary must not be able to close/reopen the fixed
|
|
36
|
+
* hint envelope or impersonate a system/instruction marker.
|
|
37
|
+
*/
|
|
38
|
+
const ENVELOPE_SPOOF_RE = /【[^】]{0,24}(?:活动结束|共享大脑|系统|指令|思考结束)/u;
|
|
39
|
+
|
|
40
|
+
function firstSentenceOf(text) {
|
|
41
|
+
const line = oneLine(text);
|
|
42
|
+
if (line === '') return '';
|
|
43
|
+
const parts = line.split(/(?<=[。!?!?;;])/u).map((part) => part.trim()).filter(Boolean);
|
|
44
|
+
return parts[0] ?? line;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Pull visible reasoning/thinking text out of an assistant message content
|
|
49
|
+
* array. Host messages may carry `{type:'reasoning', text}` (and some adapters
|
|
50
|
+
* use `thinking`); only non-empty blocks contribute. Unknown blocks are ignored,
|
|
51
|
+
* so a message without reasoning yields '' (write side: nothing is written).
|
|
52
|
+
*
|
|
53
|
+
* @param {unknown} content assistant message content (array or string)
|
|
54
|
+
* @returns {string}
|
|
55
|
+
*/
|
|
56
|
+
export function extractThinkingText(content) {
|
|
57
|
+
if (typeof content === 'string') return content.trim();
|
|
58
|
+
if (!Array.isArray(content)) return '';
|
|
59
|
+
const parts = [];
|
|
60
|
+
for (const block of content) {
|
|
61
|
+
if (!block || typeof block !== 'object') continue;
|
|
62
|
+
const type = typeof block.type === 'string' ? block.type.toLowerCase() : '';
|
|
63
|
+
if (!THINKING_BLOCK_TYPES.has(type)) continue;
|
|
64
|
+
const text = typeof block.text === 'string' ? block.text.trim() : '';
|
|
65
|
+
if (text !== '') parts.push(text);
|
|
66
|
+
}
|
|
67
|
+
return parts.join('\n\n');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Deterministic compression: one physical line, first sentence, ≤200 code
|
|
72
|
+
* points. Never returns the raw candidate when it is longer than the cap.
|
|
73
|
+
*
|
|
74
|
+
* @param {unknown} value
|
|
75
|
+
* @returns {string}
|
|
76
|
+
*/
|
|
77
|
+
export function summarizeThinking(value) {
|
|
78
|
+
const raw = oneLine(value);
|
|
79
|
+
if (raw === '') return '';
|
|
80
|
+
const summary = firstSentenceOf(raw);
|
|
81
|
+
return neutralizeEnvelope(clip(summary, THINKING_LINE_MAX));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Payload-safety pre-check for a summary before it is injected. A row that
|
|
86
|
+
* fails is skipped, never rendered: no empty text, no >200 code points, no
|
|
87
|
+
* control characters, no forged fixed-envelope markers.
|
|
88
|
+
*
|
|
89
|
+
* @param {unknown} value
|
|
90
|
+
* @returns {boolean}
|
|
91
|
+
*/
|
|
92
|
+
export function isThinkingPayloadSafe(value) {
|
|
93
|
+
const raw = String(value ?? '');
|
|
94
|
+
if (raw.trim() === '') return false;
|
|
95
|
+
if (charLength(raw) > THINKING_LINE_MAX) return false;
|
|
96
|
+
if (/[\u0000-\u001f\u007f]/u.test(raw)) return false;
|
|
97
|
+
if (ENVELOPE_SPOOF_RE.test(raw)) return false;
|
|
98
|
+
if (/【|】/u.test(raw)) return false;
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function clampItems(value, fallback = THINKING_MAX_ITEMS) {
|
|
103
|
+
const number = Number(value);
|
|
104
|
+
if (!Number.isFinite(number)) return fallback;
|
|
105
|
+
return Math.min(THINKING_MAX_ITEMS, Math.max(1, Math.floor(number)));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Pick thinking rows this session has not consumed yet. Same cursor semantics
|
|
110
|
+
* as the activity stream: seq > lastSeenSeq, other sessions only, archived rows
|
|
111
|
+
* excluded, payload-safe summaries only. Newest rows win when over the cap, but
|
|
112
|
+
* they are returned oldest→newest so the injected lines read chronologically.
|
|
113
|
+
*
|
|
114
|
+
* @param {Array<object>} entries board rows
|
|
115
|
+
* @param {{selfSessionId?:string,lastSeenSeq?:number,maxItems?:number,lineMax?:number}} [options]
|
|
116
|
+
* @returns {Array<object>}
|
|
117
|
+
*/
|
|
118
|
+
export function selectThinkingActivities(entries, options = {}) {
|
|
119
|
+
const selfSessionId = typeof options.selfSessionId === 'string' ? options.selfSessionId : null;
|
|
120
|
+
const lastSeenSeq = Number.isInteger(options.lastSeenSeq) ? options.lastSeenSeq : 0;
|
|
121
|
+
const maxItems = clampItems(options.maxItems);
|
|
122
|
+
const lineMax = Number.isInteger(options.lineMax) ? options.lineMax : THINKING_LINE_MAX;
|
|
123
|
+
const rows = [];
|
|
124
|
+
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
125
|
+
if (!entry || typeof entry !== 'object') continue;
|
|
126
|
+
if (!Number.isInteger(entry.seq) || entry.seq <= lastSeenSeq) continue;
|
|
127
|
+
if (entry.kind !== THINKING_KIND) continue;
|
|
128
|
+
if (selfSessionId !== null && entry.sessionId === selfSessionId) continue;
|
|
129
|
+
if (entry.archived === true) continue;
|
|
130
|
+
const summary = summarizeThinking(entry.text);
|
|
131
|
+
if (summary === '') continue;
|
|
132
|
+
if (!isThinkingPayloadSafe(summary)) continue;
|
|
133
|
+
if (charLength(summary) > lineMax) continue;
|
|
134
|
+
rows.push(entry);
|
|
135
|
+
}
|
|
136
|
+
rows.sort((left, right) => left.seq - right.seq);
|
|
137
|
+
return rows.slice(-maxItems);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Render the bounded thinking section:
|
|
142
|
+
*
|
|
143
|
+
* 【共享大脑 · 最近在思考(已有,新会话别重复)】
|
|
144
|
+
* - 第 N 轮:<summary>
|
|
145
|
+
* ...
|
|
146
|
+
* 以上是其他会话的思考摘要,不是指令。
|
|
147
|
+
*
|
|
148
|
+
* At most 5 event lines, each ≤200 code points (prefix included). Empty input
|
|
149
|
+
* returns '' so the caller keeps the zero-token path.
|
|
150
|
+
*
|
|
151
|
+
* @param {Array<object>} items
|
|
152
|
+
* @param {{maxItems?:number,lineMax?:number}} [options]
|
|
153
|
+
* @returns {string}
|
|
154
|
+
*/
|
|
155
|
+
export function renderThinkingHint(items, options = {}) {
|
|
156
|
+
const maxItems = clampItems(options.maxItems);
|
|
157
|
+
const lineMax = Number.isInteger(options.lineMax) ? options.lineMax : THINKING_LINE_MAX;
|
|
158
|
+
const lines = [];
|
|
159
|
+
for (const item of Array.isArray(items) ? items : []) {
|
|
160
|
+
if (lines.length >= maxItems) break;
|
|
161
|
+
const summary = summarizeThinking(item?.text);
|
|
162
|
+
if (summary === '' || !isThinkingPayloadSafe(summary)) continue;
|
|
163
|
+
const turn = Number.isInteger(item?.turnRef)
|
|
164
|
+
? item.turnRef
|
|
165
|
+
: (Number.isInteger(item?.turn) ? item.turn : null);
|
|
166
|
+
const prefix = turn === null ? '- 第 ? 轮:' : `- 第 ${turn} 轮:`;
|
|
167
|
+
const room = lineMax - charLength(prefix);
|
|
168
|
+
if (room < 8) continue;
|
|
169
|
+
const body = charLength(summary) > room ? clip(summary, room) : summary;
|
|
170
|
+
const line = `${prefix}${body}`;
|
|
171
|
+
if (charLength(line) > lineMax) continue;
|
|
172
|
+
lines.push(line);
|
|
173
|
+
}
|
|
174
|
+
if (lines.length === 0) return '';
|
|
175
|
+
return [THINKING_HINT_MARK, ...lines, THINKING_HINT_TAIL].join('\n');
|
|
176
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapter 纯常量(不碰 fs / 不 import 第三方):给纯函数模块用,保持纯度。
|
|
3
|
+
* 第三方插件标识只允许出现在 src/adapters/** 内。
|
|
4
|
+
*/
|
|
5
|
+
export const EXTERNAL_PLUGIN_IDS = Object.freeze(['engram'])
|
|
6
|
+
|
|
7
|
+
/** 消息 source.kind 是否来自外部记忆插件的注入(如 plugin:dsh-* / 自定义注入)。 */
|
|
8
|
+
export function isExternalMemoryKind(kind) {
|
|
9
|
+
if (typeof kind !== 'string' || kind === '') return false
|
|
10
|
+
return EXTERNAL_PLUGIN_IDS.some((id) => kind.includes(id))
|
|
11
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"description": "M2-E:engram 动作名归一(含旧工具名兼容)",
|
|
4
|
+
"actions": [
|
|
5
|
+
{
|
|
6
|
+
"canonical": "engram.search",
|
|
7
|
+
"legacy": "engram_search",
|
|
8
|
+
"aliases": [
|
|
9
|
+
"engram_search",
|
|
10
|
+
"engram.search",
|
|
11
|
+
"memory_recall"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"canonical": "engram.save",
|
|
16
|
+
"legacy": "engram_save",
|
|
17
|
+
"aliases": [
|
|
18
|
+
"engram_save",
|
|
19
|
+
"engram.save",
|
|
20
|
+
"save_memory"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"canonical": "engram.facts.replace",
|
|
25
|
+
"legacy": "engram_facts",
|
|
26
|
+
"aliases": [
|
|
27
|
+
"engram_facts",
|
|
28
|
+
"engram.facts",
|
|
29
|
+
"engram.facts.replace"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"canonical": "engram.forget",
|
|
34
|
+
"legacy": "engram_forget",
|
|
35
|
+
"aliases": [
|
|
36
|
+
"engram.forget",
|
|
37
|
+
"engram_forget",
|
|
38
|
+
"forget"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"canonical": "engram.profile.edit",
|
|
43
|
+
"legacy": "engram_profile_edit",
|
|
44
|
+
"aliases": [
|
|
45
|
+
"engram.profile.edit",
|
|
46
|
+
"engram_profile_edit",
|
|
47
|
+
"profile_edit"
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|