blun-king-cli 9.1.597 → 9.1.600
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 +39 -0
- package/README.md +17 -1
- package/agent-spine-plugin/CHANGELOG.md +1 -1
- package/agent-spine-plugin/docs/host-integration.md +1 -15
- package/agent-spine-plugin/docs/preflight-recall.md +1 -1
- package/agent-spine-plugin/scripts/check-install.js +1 -6
- package/agent-spine-plugin/scripts/hermetic-process.js +8 -0
- package/agent-spine-plugin/scripts/release-check.js +2 -3
- package/agent-spine-plugin/scripts/run-tests-hermetic.js +8 -11
- package/agent-spine-plugin/src/hook.js +40 -40
- package/agent-spine-plugin/src/lib/briefing.js +6 -3
- package/agent-spine-plugin/src/lib/hook-context.js +63 -64
- package/agent-spine-plugin/src/lib/hook-output.js +115 -94
- package/agent-spine-plugin/src/lib/hook-timeline.js +20 -11
- package/agent-spine-plugin/src/lib/host-instruction-budget.js +8 -14
- package/agent-spine-plugin/src/lib/mcp-timeline-tools.js +0 -7
- package/agent-spine-plugin/src/lib/pre-answer-timeline-recall.js +104 -0
- package/agent-spine-plugin/src/lib/session-timeline-codex.js +0 -4
- package/agent-spine-plugin/src/lib/session-timeline-enrollment-source.js +0 -3
- package/agent-spine-plugin/src/lib/session-timeline-enrollment-storage.js +0 -4
- package/agent-spine-plugin/src/lib/session-timeline-enrollment-transport.js +0 -2
- package/agent-spine-plugin/src/lib/session-timeline-enrollment.js +0 -22
- package/agent-spine-plugin/src/lib/session-timeline-host-origin.js +0 -4
- package/agent-spine-plugin/src/lib/session-timeline-prior.js +42 -50
- package/agent-spine-plugin/src/lib/session-timeline-provider.js +0 -3
- package/agent-spine-plugin/src/lib/session-timeline-query.js +0 -1
- package/agent-spine-plugin/src/lib/session-timeline-sid-acl.js +0 -4
- package/agent-spine-plugin/src/lib/session-timeline-source.js +0 -3
- package/agent-spine-plugin/src/lib/session-timeline-transport.js +0 -8
- package/agent-spine-plugin/src/lib/session-timeline-windows-acl.js +0 -3
- package/agent-spine-plugin/src/lib/session-timeline.js +370 -408
- package/agent-spine-plugin/src/lib/source-roots.js +26 -16
- package/agent-spine-plugin/src/lib/timeline-user-feedback.js +0 -1
- package/bin/core-bootstrap.js +18 -10
- package/bin/launcher-mode.js +5 -0
- package/bin/launcher-runtime.js +27 -0
- package/bin/verify-bundled-agent-sources.cjs +28 -2
- package/blun.mjs +798 -143
- package/bundled-agent-sources.json +192 -155
- package/package.json +1 -1
- package/standard-skills/translate-native/README.md +1334 -1
- package/standard-skills/translate-native/SKILL.md +1 -1
- package/standard-skills/translate-native/VERSION +1 -1
- package/standard-skills/translate-native/docs/PREMORTEM.md +901 -0
- package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION.md +208 -41
- package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_API.md +1319 -77
- package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_EVIDENCE_HTTP.md +66 -11
- package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_HTTP_PROVIDER.md +19 -1
- package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_RECEIPT_VERIFIER_HTTP.md +24 -6
- package/standard-skills/translate-native/integrations/commercial_localization_profile.py +23 -0
- package/standard-skills/translate-native/integrations/website_localization.py +234 -10
- package/standard-skills/translate-native/integrations/website_localization_benchmark.py +39 -3
- package/standard-skills/translate-native/integrations/website_localization_cms.py +512 -10
- package/standard-skills/translate-native/integrations/website_localization_cms_client.py +116 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_receiver.py +101 -11
- package/standard-skills/translate-native/integrations/website_localization_cms_receiver_runtime.py +12 -1
- package/standard-skills/translate-native/integrations/website_localization_cms_receiver_store.py +186 -24
- package/standard-skills/translate-native/integrations/website_localization_cms_source_client.py +64 -1
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery.py +528 -5
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_auth.py +918 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_auth_runtime.py +609 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_client.py +885 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_http.py +1244 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_runtime.py +220 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_sidecar_adapter.py +331 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_capabilities_http.py +444 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_client.py +673 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch.py +1317 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_client.py +826 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_http.py +1152 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_openapi.py +896 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_runtime.py +621 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_http.py +1180 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_runtime.py +1479 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_source_http.py +133 -17
- package/standard-skills/translate-native/integrations/website_localization_cms_source_runtime.py +319 -4
- package/standard-skills/translate-native/integrations/website_localization_cms_source_service.py +72 -2
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_http.py +13 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver.py +56 -13
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver_openapi.py +335 -0
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver_runtime.py +66 -1
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_processing_monitor.py +73 -7
- package/standard-skills/translate-native/integrations/website_localization_cms_terminal_receiver_client.py +93 -9
- package/standard-skills/translate-native/integrations/website_localization_evidence_http.py +221 -11
- package/standard-skills/translate-native/integrations/website_localization_health.py +6 -0
- package/standard-skills/translate-native/integrations/website_localization_http_provider.py +1 -1
- package/standard-skills/translate-native/integrations/website_localization_quality_profiles.py +350 -0
- package/standard-skills/translate-native/integrations/website_localization_queue.py +126 -56
- package/standard-skills/translate-native/integrations/website_localization_receipt_verifier_http.py +166 -13
- package/standard-skills/translate-native/integrations/website_localization_release.py +423 -17
- package/standard-skills/translate-native/integrations/website_localization_release_coordinator.py +168 -13
- package/standard-skills/translate-native/integrations/website_localization_runner.py +10 -0
- package/standard-skills/translate-native/integrations/website_localization_worker.py +213 -9
- package/standard-skills/translate-native/provenance.json +2 -2
- package/standard-skills/translate-native/references/commercial-localization.md +96 -17
- package/standard-skills/translate-native/scripts/blun_language_guard.py +3 -1
- package/standard-skills/translate-native/scripts/check_commercial_review.py +69 -9
- package/standard-skills/translate-native/scripts/commercial_localization_profile.py +829 -31
- package/standard-tools/language-guard/VERSION +1 -1
- package/standard-tools/language-guard/blun_language_guard.py +3 -1
- package/standard-tools/language-guard/check_commercial_review.py +69 -9
- package/standard-tools/language-guard/commercial_localization_profile.py +829 -31
- package/standard-tools/language-guard/provenance.json +2 -2
- package/standard-tools/manifest.json +1 -1
- package/worker-host.mjs +215 -82
- package/agent-spine-plugin/scripts/check-install-king.js +0 -37
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# BLUN King Changelog
|
|
2
2
|
|
|
3
|
+
## 9.1.600
|
|
4
|
+
|
|
5
|
+
- Bound each response-review attempt to 120 seconds while retaining the 30-second default for generic reviewers and the existing maximum of two outer attempts.
|
|
6
|
+
- Allow up to 90 seconds for first meaningful native-review text once per attempt, then renew a 30-second idle budget on meaningful text and ordered release and verification stages without extending the absolute deadline.
|
|
7
|
+
- Preserve cancellation, exact response binding, receipt expiry, and authorization; these changes do not establish a fix for live HTTP 502 errors.
|
|
8
|
+
- Pin AgentSpine 0.73.0 and Translate Native 6.155.0; keep the guard protocol at 6.20.0.
|
|
9
|
+
|
|
10
|
+
## 9.1.599
|
|
11
|
+
|
|
12
|
+
Local candidate; not published or installed.
|
|
13
|
+
|
|
14
|
+
- Show localized personal-memory actions and complete previews of settings changes or remembered text, with terminal control characters escaped.
|
|
15
|
+
- Offer one-shot approval for valid memory changes and rejection only for invalid inputs.
|
|
16
|
+
- Prevent session-wide approval from automatically accepting queued requests that require individual approval.
|
|
17
|
+
|
|
18
|
+
## 9.1.598
|
|
19
|
+
|
|
20
|
+
- Distinguish lower-bound input counts from exact counts in upstream context rejections.
|
|
21
|
+
- Reduce the output allowance once for a lower-bound rejection without discarding messages or tools; retain cancellation and bounded retry behavior.
|
|
22
|
+
- Verify moving tokenization thresholds and exact-count counterexamples in the compiled program.
|
|
23
|
+
- Include the previously missing package notes for 9.1.595 through 9.1.597.
|
|
24
|
+
- Restore the original 150 rotating activity words without additional model requests.
|
|
25
|
+
- Keep media payloads out of AgentSpine's text-only hook input while preserving complete model attachments and policy checks.
|
|
26
|
+
|
|
27
|
+
## 9.1.597
|
|
28
|
+
|
|
29
|
+
- Classify explicit context-limit errors wrapped in HTTP 502 and allow one corrected request with a smaller output allowance.
|
|
30
|
+
- Preserve messages, tools, cancellation, and the rule against replaying an already-started response.
|
|
31
|
+
- Known limitation: this initial correction treated lower-bound input counts as exact. The additional correction is included in 9.1.598.
|
|
32
|
+
|
|
33
|
+
## 9.1.596
|
|
34
|
+
|
|
35
|
+
- Distinguish owner cancellation from automatic turn interruption.
|
|
36
|
+
|
|
37
|
+
## 9.1.595
|
|
38
|
+
|
|
39
|
+
- Defer tool and skill descriptions until they are needed, while keeping discovery available.
|
|
40
|
+
- Bound greeting context without deleting saved task history or weakening permissions.
|
|
41
|
+
|
|
3
42
|
## 9.1.594
|
|
4
43
|
|
|
5
44
|
- Preserve private Telegram replies while group messages wait, and retain slash-command routing.
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Voraussetzung ist Node.js 24.15 oder neuer. Die geprüfte Version wird exakt
|
|
|
9
9
|
installiert:
|
|
10
10
|
|
|
11
11
|
```powershell
|
|
12
|
-
npm install -g blun-king-cli@9.1.
|
|
12
|
+
npm install -g blun-king-cli@9.1.600
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
## Reproduzierbares Staging und Packen
|
|
@@ -40,6 +40,22 @@ eingerichtet. Die Anmeldung erfolgt anschließend in der
|
|
|
40
40
|
Konsole mit `/login` über den BLUN-OAuth-Server. Das Paket erzeugt keine
|
|
41
41
|
statische Anbieter- oder API-Key-Konfiguration.
|
|
42
42
|
|
|
43
|
+
## Persönliches Gedächtnis
|
|
44
|
+
|
|
45
|
+
Seit Version 9.1.599 zeigt BLUN King Freigaben für das persönliche Gedächtnis mit übersetzten Aktionstiteln und vollständigen Vorschauen der tatsächlichen Einstellungsänderungen oder des zu speichernden Textes. Gültige Änderungen lassen sich nur einmalig freigeben, ungültige Eingaben nur ablehnen. Eine Sitzungsfreigabe genehmigt solche wartenden Anfragen nicht automatisch. Steuerzeichen werden sichtbar maskiert; der Originalinhalt bleibt unverändert.
|
|
46
|
+
|
|
47
|
+
## Zeitgrenzen für die Antwortprüfung
|
|
48
|
+
|
|
49
|
+
Version 9.1.600 begrenzt die Antwortprüfung pro äußerem Prüfversuch auf insgesamt 120 Sekunden. Allgemeine Prüfer behalten die bisherige Frist von 30 Sekunden. Bei der nativen Sprachprüfung stehen nach dem bestätigten Start einmalig bis zu 90 Sekunden für den ersten inhaltlichen Text bereit; auch dabei gilt die Gesamtgrenze. Inhaltlicher Text innerhalb der zulässigen Ausgabelänge sowie die geordneten Übergänge zur Freigabe und zur Belegprüfung setzen die Wartefrist jeweils auf 30 Sekunden zurück, ohne die Gesamtgrenze zu verlängern. Es bleibt bei höchstens zwei äußeren Prüfversuchen.
|
|
50
|
+
|
|
51
|
+
Abbruch, exakte Bindung an den Antworttext, Belegablauf und Autorisierung bleiben unverändert. Damit ist keine Behebung von HTTP-502-Fehlern im Live-Betrieb nachgewiesen.
|
|
52
|
+
|
|
53
|
+
## Festgelegte Komponentenstände
|
|
54
|
+
|
|
55
|
+
- AgentSpine 0.73.0, Commit `38cb94760b19e3a82e7efdf12538a1adf5324964`.
|
|
56
|
+
- Translate Native 6.155.0, Commit `21efc66da8f77992c8c63ac0f145a106c31e7eb2`.
|
|
57
|
+
- Guard-Protokoll unverändert auf 6.20.0.
|
|
58
|
+
|
|
43
59
|
## Nachweisbare Arbeitsabläufe
|
|
44
60
|
|
|
45
61
|
Version 9.1.0 enthält sieben zusätzliche, getrennt nutzbare Befehlsgruppen:
|
|
@@ -58,7 +58,7 @@ All notable changes to AgentSpine will be documented here. The project follows [
|
|
|
58
58
|
|
|
59
59
|
### Fixed
|
|
60
60
|
|
|
61
|
-
- King
|
|
61
|
+
- King reader overflow stays explicit and non-blocking; sources remain unchanged.
|
|
62
62
|
- The portal/thread lifecycle probe that starts a fresh process now runs outside the shared Windows I/O pool. Its existing assertions and process deadlines are unchanged.
|
|
63
63
|
- The hermetic runner isolates the real host-origin source-attestation probe from concurrent Windows filesystem pressure, preserving its existing fail-closed result and timing boundaries without increasing a timeout.
|
|
64
64
|
- Reuse host-verified, assignment-bound briefing observations in MCP delivery preparation without a second content fetch; keep missing proof advisory and document provider-specific history limits.
|
|
@@ -10,7 +10,7 @@ Provider evidence is not interchangeable. Each adapter separately binds its life
|
|
|
10
10
|
|---|---|---|---|
|
|
11
11
|
| Project instructions | `CLAUDE.md` hierarchy | `AGENTS.md` hierarchy | `AGENTS.md` through the isolated BLUN profile |
|
|
12
12
|
| Hook package | Native `hooks/hooks.json` | Explicit `hooks/codex.json` | Manifest lifecycle hooks |
|
|
13
|
-
| Verified hook briefing → delivery knowledge | Native `additionalContext` handoff receipt | Native `additionalContext` handoff receipt |
|
|
13
|
+
| Verified hook briefing → delivery knowledge | Native `additionalContext` handoff receipt | Native `additionalContext` handoff receipt | Native bounded `message` handoff receipt; live acceptance open |
|
|
14
14
|
| Historical transcript enrollment | Bounded registered Claude source | Bounded Codex `sessions` source with verified headers | Protected King agent-wire mapping; live mapping open |
|
|
15
15
|
| Cross-provider evidence handoff | Explicit opt-in; Claude provenance retained | Explicit opt-in; Codex provenance retained | Same contract; live launcher unverified |
|
|
16
16
|
| Native permissions/trust | Host-owned | Host-owned | Host-owned; enforcement of returned block decisions remains externally unverified |
|
|
@@ -19,20 +19,6 @@ Provider evidence is not interchangeable. Each adapter separately binds its life
|
|
|
19
19
|
|
|
20
20
|
Adapter acceptance uses bounded synthetic A/B sessions and covers restart, source mutation, foreign scope, private exclusion and service failure. Cross-provider recall still needs local opt-in plus `includePriorProviders: true` with `includePriorSessions: true`. Report repository, installed-host and model observations separately.
|
|
21
21
|
|
|
22
|
-
King preparation, lifecycle receipts and process advisories do not emit visible
|
|
23
|
-
`message` prompts. Required instructions remain byte-exact; `UserPromptSubmit`
|
|
24
|
-
uses model-only `additionalContext`. Other events also return that quiet field;
|
|
25
|
-
whether the host consumes it is a separate lifecycle contract. Actual deny/block
|
|
26
|
-
decisions retain their original error schema.
|
|
27
|
-
King uses the existing source-reader safety bounds of 4 MiB per document and
|
|
28
|
-
8 MiB for the source set, not Codex's 32 KiB instruction cap. When both King
|
|
29
|
-
environment bindings are present, `BLUN_HOME` owns the profile even if an unrelated
|
|
30
|
-
`CODEX_HOME` is inherited. These are resource limits, not recommended file sizes.
|
|
31
|
-
Codex/King internal hook envelopes account for the serialized verified briefing,
|
|
32
|
-
including JSON escaping. King's emitted runtime context remains at most 1200
|
|
33
|
-
bytes; its native instruction loader separately supplies full instructions.
|
|
34
|
-
Claude's existing instruction and context limits remain unchanged.
|
|
35
|
-
|
|
36
22
|
## Claude Code
|
|
37
23
|
|
|
38
24
|
| Component | Path | Purpose |
|
|
@@ -6,7 +6,7 @@ The receipt binds agent and optional persona, user, tenant, host, instruction ho
|
|
|
6
6
|
|
|
7
7
|
## Host instructions
|
|
8
8
|
|
|
9
|
-
Claude Code uses its resolved user and project `CLAUDE.md` hierarchy. Codex uses the corresponding `AGENTS.override.md`/`AGENTS.md` hierarchy. A generic host must explicitly bind `instruction_host` to `claude` or `codex`; AgentSpine does not guess. The mandatory preflight section contains the complete bytes of every
|
|
9
|
+
Claude Code uses its resolved user and project `CLAUDE.md` hierarchy. Codex uses the corresponding `AGENTS.override.md`/`AGENTS.md` hierarchy. A generic host must explicitly bind `instruction_host` to `claude` or `codex`; AgentSpine does not guess. The mandatory preflight section contains the complete bytes of every loaded instruction document. The standard budget is 8 KiB, with aggregate overflow up to 16 KiB for Claude or 32 KiB for Codex. King uses its own profile and bounded source-reader resources; a rule outside that resource budget remains explicitly unverified without blocking the native-host turn. Loaded bytes, overflow and hard limit are bound into the signed exact-turn receipt and revalidated before consumption. Unreadable, replaced, deleted, out-of-scope or symlinked mandatory files still fail closed.
|
|
10
10
|
|
|
11
11
|
Claude Code's `InstructionsLoaded` lifecycle event is registered as an additional observability signal, while `UserPromptSubmit` remains the blocking and injection boundary. Codex uses its own manifest-selected hook set without that unsupported Claude-only event. The preflight does not rely on the model remembering to read a file or call a tool.
|
|
12
12
|
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
invokeInstalledBlockingProtocols, invokeInstalledBlunPostWriteDigest, invokeInstalledHook
|
|
13
13
|
} from "./check-install-hook.js";
|
|
14
14
|
import { invokeInstalledSelfstarter as runInstalledSelfstarter } from "./check-install-selfstarter.js";
|
|
15
|
-
import { invokeInstalledKingInstructions } from "./check-install-king.js";
|
|
16
15
|
|
|
17
16
|
function hash(value) {
|
|
18
17
|
return createHash("sha256").update(value).digest("hex");
|
|
@@ -365,15 +364,13 @@ export async function checkInstall(root = process.cwd()) {
|
|
|
365
364
|
await mkdir(userProject, { recursive: true });
|
|
366
365
|
await writeFile(source, "# Existing soul\n\nNever modify me.\n", "utf8");
|
|
367
366
|
await writeFile(join(userProject, "CLAUDE.md"), "# Installed Claude rules\n\nLoad this before every answer.\n", "utf8");
|
|
368
|
-
|
|
369
|
-
await writeFile(join(userProject, "AGENTS.md"), largeCodexRules, "utf8");
|
|
367
|
+
await writeFile(join(userProject, "AGENTS.md"), `# Installed Codex rules\n\n${"x".repeat(17565)}`, "utf8");
|
|
370
368
|
const protectedInstallSources = [source, join(userProject, "CLAUDE.md"), join(userProject, "AGENTS.md")];
|
|
371
369
|
const sourceHashes = new Map(await Promise.all(protectedInstallSources.map(async (path) => [path, hash(await readFile(path))])));
|
|
372
370
|
|
|
373
371
|
const fresh = join(workspace, "fresh", "agent-spine");
|
|
374
372
|
await copyBundle(root, fresh);
|
|
375
373
|
const freshResult = await checkHosts(fresh);
|
|
376
|
-
const freshKing = await invokeInstalledKingInstructions(fresh, join(workspace, "king-fresh"));
|
|
377
374
|
const aliasRoot = join(workspace, "fresh-alias");
|
|
378
375
|
await symlink(join(workspace, "fresh"), aliasRoot, process.platform === "win32" ? "junction" : "dir");
|
|
379
376
|
const aliasResult = await checkHosts(join(aliasRoot, "agent-spine"));
|
|
@@ -413,7 +410,6 @@ export async function checkInstall(root = process.cwd()) {
|
|
|
413
410
|
await removeTree(installed);
|
|
414
411
|
await rename(staging, installed);
|
|
415
412
|
const upgraded = await checkHosts(installed);
|
|
416
|
-
const upgradedKing = await invokeInstalledKingInstructions(installed, join(workspace, "king-upgrade"));
|
|
417
413
|
const upgradeState = join(workspace, "state-upgrade");
|
|
418
414
|
const upgradedHook = await invokeInstalledHook(installed, userProject, upgradeState, "codex");
|
|
419
415
|
const upgradedBlockingProtocols = await invokeInstalledBlockingProtocols(
|
|
@@ -443,7 +439,6 @@ export async function checkInstall(root = process.cwd()) {
|
|
|
443
439
|
automaticBriefing: { fresh: freshHook, upgrade: upgradedHook },
|
|
444
440
|
blockingProtocols: { fresh: freshBlockingProtocols, upgrade: upgradedBlockingProtocols },
|
|
445
441
|
blunPostWriteDigest: { fresh: freshBlunPost, upgrade: upgradedBlunPost },
|
|
446
|
-
kingInstructions: { fresh: freshKing, upgrade: upgradedKing },
|
|
447
442
|
automaticAttention: { fresh: freshAttention, upgrade: upgradedAttention },
|
|
448
443
|
automaticSelfstarter: { fresh: freshSelfstarter, upgrade: upgradedSelfstarter },
|
|
449
444
|
automaticChannelWake: { fresh: freshChannelWake, upgrade: upgradedChannelWake },
|
|
@@ -6,6 +6,14 @@ export const TIMEOUT_EXIT_CODE = 124;
|
|
|
6
6
|
const MAX_CAPTURE_CHARS = 1_000_000;
|
|
7
7
|
const TASKKILL_TIMEOUT_MS = 5_000;
|
|
8
8
|
|
|
9
|
+
export function selectTestShard(files, spec) {
|
|
10
|
+
if (!spec) return files;
|
|
11
|
+
const match = /^([1-9]\d*)\/([1-9]\d*)$/.exec(spec);
|
|
12
|
+
const index = +match?.[1]; const count = +match?.[2];
|
|
13
|
+
if (!match || count < 2 || index > count) throw new Error("SHARD");
|
|
14
|
+
return files.filter((_, i) => i % count === index - 1);
|
|
15
|
+
}
|
|
16
|
+
|
|
9
17
|
function running(child) {
|
|
10
18
|
return child?.pid && child.exitCode === null && child.signalCode === null;
|
|
11
19
|
}
|
|
@@ -8,8 +8,7 @@ const SEMVER_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?
|
|
|
8
8
|
// Includes the bounded user-feedback candidate path; this is not semantic acceptance.
|
|
9
9
|
// Measured code growth before documentation: 1874 packed / 7830 unpacked bytes.
|
|
10
10
|
const MAX_PACKED_BYTES = 544 * 1024;
|
|
11
|
-
|
|
12
|
-
const MAX_UNPACKED_BYTES = 2434 * 1024;
|
|
11
|
+
const MAX_UNPACKED_BYTES = 2430 * 1024;
|
|
13
12
|
const REQUIRED_PACKAGE_FILES = [
|
|
14
13
|
"blun.plugin.json", ".claude-plugin/plugin.json", ".codex-plugin/plugin.json", ".mcp.json",
|
|
15
14
|
"CHANGELOG.md", "LICENSE", "README.md", "bin/agentspine.js", "bin/agentspine-mcp.js",
|
|
@@ -79,7 +78,7 @@ function validatePackageReport(report, version) {
|
|
|
79
78
|
assert(typeof item.integrity === "string" && item.integrity.startsWith("sha512-"), "npm pack did not report SHA-512 integrity");
|
|
80
79
|
assert(Number.isInteger(item.entryCount) && item.entryCount > 0 && item.entryCount <= 500, "npm package file count is outside the release limit");
|
|
81
80
|
assert(Number.isInteger(item.size) && item.size > 0 && item.size <= MAX_PACKED_BYTES, "npm package exceeds the 544 KiB packed release limit");
|
|
82
|
-
assert(Number.isInteger(item.unpackedSize) && item.unpackedSize > 0 && item.unpackedSize <= MAX_UNPACKED_BYTES, "npm package exceeds the
|
|
81
|
+
assert(Number.isInteger(item.unpackedSize) && item.unpackedSize > 0 && item.unpackedSize <= MAX_UNPACKED_BYTES, "npm package exceeds the 2430 KiB unpacked release limit");
|
|
83
82
|
const paths = (item.files || []).map((file) => file.path);
|
|
84
83
|
assert(paths.length === new Set(paths).size, "npm package contains duplicate paths");
|
|
85
84
|
for (const path of REQUIRED_PACKAGE_FILES) assert(paths.includes(path), `npm package is missing required file: ${path}`);
|
|
@@ -4,16 +4,14 @@ import { mkdir, mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
|
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { githubErrorCommand } from "./github-actions.js";
|
|
7
|
-
import { configuredTestTimeout, runBoundedProcess } from "./hermetic-process.js";
|
|
7
|
+
import { configuredTestTimeout, runBoundedProcess, selectTestShard } from "./hermetic-process.js";
|
|
8
8
|
|
|
9
9
|
const root = resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
10
10
|
const base = await mkdtemp(join(tmpdir(), "agentspine-hermetic-tests-"));
|
|
11
|
-
const testFiles = (await readdir(join(root, "test")))
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const concurrencyCeiling = process.platform === "win32" ? 2 : 4;
|
|
16
|
-
const concurrency = Math.max(1, Math.min(concurrencyCeiling, cpus().length));
|
|
11
|
+
const testFiles = selectTestShard((await readdir(join(root, "test")))
|
|
12
|
+
.filter((name) => name.endsWith(".test.js")).sort(), process.env.AGENTSPINE_TEST_SHARD);
|
|
13
|
+
const win=process.platform==="win32";
|
|
14
|
+
const concurrency = Math.max(1, Math.min(win ? 3 : 4, cpus().length));
|
|
17
15
|
const testTimeoutMs = configuredTestTimeout();
|
|
18
16
|
const slowTestTimeouts = new Map([
|
|
19
17
|
["indexed-memory.test.js", 300_000]
|
|
@@ -55,10 +53,6 @@ async function runOne(file, mode, index) {
|
|
|
55
53
|
async function runMode(mode) {
|
|
56
54
|
const indexed = testFiles.map((file, index) => ({ file, index }));
|
|
57
55
|
const results = [];
|
|
58
|
-
// Installation copies complete bundles, while the MCP and route-lifecycle tests
|
|
59
|
-
// start real children with fixed deadlines and host-origin enrollment attests a
|
|
60
|
-
// live source plus signed state. Keep those probes off the shared Windows I/O pool.
|
|
61
|
-
// Both profiles still run them once, with unchanged assertions and limits.
|
|
62
56
|
const isolatedNames = new Set([
|
|
63
57
|
"assignment-continuation-boundaries.test.js",
|
|
64
58
|
"assignment-continuation.test.js",
|
|
@@ -80,6 +74,9 @@ async function runMode(mode) {
|
|
|
80
74
|
"session-timeline-invocation.test.js",
|
|
81
75
|
"timeline-natural-feedback.test.js"
|
|
82
76
|
]);
|
|
77
|
+
if (win) for (const { file } of indexed) {
|
|
78
|
+
if (file.includes("timeline") || file === "preflight-large-agents.test.js") isolatedNames.add(file);
|
|
79
|
+
}
|
|
83
80
|
const isolated = indexed.filter(item => isolatedNames.has(item.file));
|
|
84
81
|
const queue = indexed.filter(item => !isolatedNames.has(item.file));
|
|
85
82
|
for (const item of isolated) results.push(await runOne(item.file, mode, item.index));
|
|
@@ -13,22 +13,18 @@ import { syncPersonaRosterFromEnvironment } from "./lib/persona-runtime.js";
|
|
|
13
13
|
import { captureMustRememberPrompt, recordPreflightFailure, runPreflight, verifyPreflightReceipt } from "./lib/preflight.js";
|
|
14
14
|
import { actionLessonRecall } from "./lib/action-lesson-recall.js";
|
|
15
15
|
import { captureSessionTimelineLifecycle, finalizeUserPromptSessionTimeline } from "./lib/hook-timeline.js";
|
|
16
|
+
import { fitTimelineRecallToHostContext } from "./lib/pre-answer-timeline-recall.js";
|
|
16
17
|
import { timelineToolKind } from "./lib/mcp-timeline-tools.js";
|
|
17
18
|
import { emitTimelineToolGuard, runTimelineToolGuard } from "./lib/timeline-tool-guard.js";
|
|
18
19
|
import { readHookInput, SILENT_OVERSIZE_POST_TOOL_USE, SILENT_OVERSIZE_POST_TOOL_USE_ARG } from "./lib/hook-input.js";
|
|
19
20
|
import { isMainModule } from "./lib/runtime.js";
|
|
20
|
-
import { deliveryActorSession, deliverySuccessEvidence, recordDeliveryToolUse,
|
|
21
|
-
|
|
22
|
-
import { blockPrompt, blockStop, blunRuntimeContext, blunRuntimeMessage, denyTool, hookOutput,
|
|
23
|
-
lifecycleOutput } from "./lib/hook-output.js";
|
|
21
|
+
import { deliveryActorSession, deliverySuccessEvidence, recordDeliveryToolUse, recordDeliveryWriteIntent } from "./lib/delivery-verification.js";
|
|
22
|
+
import { blockPrompt, blockStop, blunRuntimeContext, blunRuntimeMessage, denyTool, hookOutput, lifecycleOutput } from "./lib/hook-output.js";
|
|
24
23
|
import { ATTENTION_WRITE_EVENTS, boundedId, captureAttentionLifecycle, hookDeliveryId, hostContextLimit, hostFromInput,
|
|
25
|
-
promptFromInput, renderContext, runtimeScope, selfstarterInput, selfstarterRootSkipped,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
import {
|
|
29
|
-
isMutationTool, isScanFailOpenTool, shellTargetsProtected } from "./lib/hook-protection.js";
|
|
30
|
-
import { captureJavaScriptBeforeWrite, inspectWrittenJavaScript, verifyBaselineBeforeWrite,
|
|
31
|
-
verifyDeliveredArtifacts } from "./lib/hook-artifact-guards.js";
|
|
24
|
+
promptFromInput, renderContext, runtimeScope, selfstarterInput, selfstarterRootSkipped, selfstarterScope, sessionId,
|
|
25
|
+
startChannelEvent, startSelfstarter, toolResult, toolSucceeded } from "./lib/hook-context.js";
|
|
26
|
+
import { auditGuard, auditSkippedScans, candidatePaths, finishScanFailure, hookScanFailureFailsOpen, isMutationTool, isScanFailOpenTool, shellTargetsProtected } from "./lib/hook-protection.js";
|
|
27
|
+
import { captureJavaScriptBeforeWrite, inspectWrittenJavaScript, verifyBaselineBeforeWrite, verifyDeliveredArtifacts } from "./lib/hook-artifact-guards.js";
|
|
32
28
|
import { isPremortemWrite, prepareHookPremortem, recordHookPremortemWrite,
|
|
33
29
|
recordHookPremortemWriteIntent, verifyHookPremortemWrite } from "./lib/hook-premortem.js";
|
|
34
30
|
import { verifyHookStopContracts } from "./lib/hook-stop-verification.js";
|
|
@@ -38,7 +34,7 @@ export { blunRuntimeContext, blunRuntimeMessage } from "./lib/hook-output.js";
|
|
|
38
34
|
const CONTEXT_EVENTS = new Set(["SessionStart", "UserPromptSubmit", "PreCompact", "PostCompact"]);
|
|
39
35
|
const KNOWN_EVENTS = new Set([...CONTEXT_EVENTS, "InstructionsLoaded", "PreToolUse", "PostToolUse", "Stop", "SubagentStop"]);
|
|
40
36
|
async function writeContextOutput(output, host) {
|
|
41
|
-
const field = Object.hasOwn(output.hookSpecificOutput, "
|
|
37
|
+
const field = Object.hasOwn(output.hookSpecificOutput, "additionalContext") ? "additionalContext" : "message", content = output.hookSpecificOutput[field];
|
|
42
38
|
await new Promise((resolveWrite, rejectWrite) => process.stdout.write(`${JSON.stringify(output)}\n`, (error) => error ? rejectWrite(error) : resolveWrite()));
|
|
43
39
|
return { schema: "agentspine.host-context-handoff/v1", host, field, bytes: Buffer.byteLength(content), digest: createHash("sha256").update(content).digest("hex") };
|
|
44
40
|
}
|
|
@@ -99,17 +95,15 @@ async function runHookCore(input, payload, options) {
|
|
|
99
95
|
}
|
|
100
96
|
const root = resolvedSources.projectRoot;
|
|
101
97
|
const catalog = resolvedSources.catalog;
|
|
102
|
-
const catalogPath
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
let
|
|
108
|
-
let artifactGuard = null;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (event === "PreToolUse" && isScanFailOpenTool(input.tool_name) && resolvedSources.diagnostics.skipped?.length) {
|
|
112
|
-
await auditSkippedScans(input, "source-resolution", resolvedSources.diagnostics.skipped);
|
|
98
|
+
const diagnostics=resolvedSources.diagnostics,catalogPath=await saveCatalog(catalog),sourceWarning=[...new Set([diagnostics.warning,...resolvedSources.unverified.map((item)=>item.message),...(diagnostics.warnings||[]).map((item)=>item.message)].filter(Boolean))].join("\nAgentSpine source warning: ")||null;
|
|
99
|
+
if (CONTEXT_EVENTS.has(event) && resolvedSources.unverified.length && !catalog.summary.total) {
|
|
100
|
+
if (payload) return { blocked: false, sourceUnverified: true, sourceWarning };
|
|
101
|
+
process.stdout.write(`${JSON.stringify({ hookSpecificOutput: { hookEventName: event, message: sourceWarning } })}\n`); return;
|
|
102
|
+
}
|
|
103
|
+
let scope = null, selfstarter = null, channelEvent = null, learningDelivery = null;
|
|
104
|
+
let deliveryVerification = null, artifactGuard = null, premortem = null, lessonRecall = null;
|
|
105
|
+
if (event === "PreToolUse" && isScanFailOpenTool(input.tool_name) && diagnostics.skipped?.length) {
|
|
106
|
+
await auditSkippedScans(input, "source-resolution", diagnostics.skipped);
|
|
113
107
|
}
|
|
114
108
|
if (event === "PreToolUse" && isMutationTool(input.tool_name)) {
|
|
115
109
|
const { graph } = await loadGraph(root, catalog);
|
|
@@ -129,7 +123,8 @@ async function runHookCore(input, payload, options) {
|
|
|
129
123
|
}
|
|
130
124
|
}
|
|
131
125
|
}
|
|
132
|
-
const protectedDocuments = catalog.documents.filter((doc) => protectedRelative.has(doc.relativePath))
|
|
126
|
+
const protectedDocuments = [...catalog.documents.filter((doc) => protectedRelative.has(doc.relativePath)),
|
|
127
|
+
...resolvedSources.unverified.map((item) => ({ path: item.path, relativePath: item.id }))];
|
|
133
128
|
const protectedPaths = new Set(protectedDocuments.map((doc) => resolve(doc.path)));
|
|
134
129
|
const targets = await Promise.all(candidatePaths(input.tool_input || input.tool_args).map(async (path) => {
|
|
135
130
|
const target = resolve(cwd, path);
|
|
@@ -141,7 +136,8 @@ async function runHookCore(input, payload, options) {
|
|
|
141
136
|
const hit = targets.find((path) => protectedPaths.has(path));
|
|
142
137
|
const shellHit = shellTargetsProtected(input, protectedDocuments, cwd, root);
|
|
143
138
|
if (hit || shellHit) {
|
|
144
|
-
const relativePath = shellHit?.relativePath
|
|
139
|
+
const relativePath = shellHit?.relativePath
|
|
140
|
+
|| protectedDocuments.find((doc) => resolve(doc.path) === hit)?.relativePath || hit;
|
|
145
141
|
const reason = `AgentSpine protected source: ${relativePath}. Existing identity, rule, soul, and memory documents are read-only to agents.`;
|
|
146
142
|
if (payload) return { blocked: true, reason };
|
|
147
143
|
denyTool(reason);
|
|
@@ -170,7 +166,7 @@ async function runHookCore(input, payload, options) {
|
|
|
170
166
|
});
|
|
171
167
|
}
|
|
172
168
|
}
|
|
173
|
-
if (event === "PreToolUse" && !selfstarterRootSkipped(
|
|
169
|
+
if (event === "PreToolUse" && !selfstarterRootSkipped(diagnostics)) {
|
|
174
170
|
try {
|
|
175
171
|
scope ||= await runtimeScope(input, root, resolvedSources.userStateRoot, catalog);
|
|
176
172
|
const exact = await selfstarterScope(input, scope, root, "effect");
|
|
@@ -241,7 +237,7 @@ async function runHookCore(input, payload, options) {
|
|
|
241
237
|
}
|
|
242
238
|
}
|
|
243
239
|
} else {
|
|
244
|
-
const activeJob = selfstarterRootSkipped(
|
|
240
|
+
const activeJob = selfstarterRootSkipped(diagnostics)
|
|
245
241
|
? null : await selfstarterScope(input, scope, root, "resume");
|
|
246
242
|
const pauseRequested = Boolean(activeJob && selfstarterInput(input)?.status !== "completed");
|
|
247
243
|
let contracts = await verifyHookStopContracts({
|
|
@@ -285,7 +281,7 @@ async function runHookCore(input, payload, options) {
|
|
|
285
281
|
scope = await runtimeScope(input, root, resolvedSources.userStateRoot, catalog);
|
|
286
282
|
attentionEvent = await captureAttentionLifecycle(input, event, root, scope, catalog);
|
|
287
283
|
}
|
|
288
|
-
if (event === "PostToolUse" && !selfstarterRootSkipped(
|
|
284
|
+
if (event === "PostToolUse" && !selfstarterRootSkipped(diagnostics)) {
|
|
289
285
|
scope ||= await runtimeScope(input, root, resolvedSources.userStateRoot, catalog);
|
|
290
286
|
const exact = await selfstarterScope(input, scope, root, "effect");
|
|
291
287
|
if (exact) {
|
|
@@ -297,7 +293,7 @@ async function runHookCore(input, payload, options) {
|
|
|
297
293
|
}
|
|
298
294
|
if (["Stop", "SubagentStop"].includes(event)) {
|
|
299
295
|
scope ||= await runtimeScope(input, root, resolvedSources.userStateRoot, catalog);
|
|
300
|
-
const exact = selfstarterRootSkipped(
|
|
296
|
+
const exact = selfstarterRootSkipped(diagnostics)
|
|
301
297
|
? null : await selfstarterScope(input, scope, root, "resume");
|
|
302
298
|
if (exact && !scope.currentTaskId) scope.currentTaskId = exact.taskId;
|
|
303
299
|
try {
|
|
@@ -327,12 +323,12 @@ async function runHookCore(input, payload, options) {
|
|
|
327
323
|
await syncPersonaRosterFromEnvironment({ root, env: process.env, now: input.timestamp || new Date(), catalog });
|
|
328
324
|
scope ||= await runtimeScope(input, root, resolvedSources.userStateRoot, catalog);
|
|
329
325
|
if (event !== "UserPromptSubmit") {
|
|
330
|
-
try {
|
|
326
|
+
try { diagnostics.timeline = await captureSessionTimelineLifecycle({ root, event, input, scope,
|
|
331
327
|
hostHome: resolvedSources.hostHome, catalog }); }
|
|
332
|
-
catch (error) {
|
|
328
|
+
catch (error) { diagnostics.timeline = { status: "degraded", reason: error.message, authority: "context-only" }; }
|
|
333
329
|
}
|
|
334
330
|
channelEvent = await startChannelEvent(input, event, root, scope, catalog);
|
|
335
|
-
selfstarter = await startSelfstarter(input, event, root, scope,
|
|
331
|
+
selfstarter = await startSelfstarter(input, event, root, scope, diagnostics);
|
|
336
332
|
if (selfstarter?.job && !scope.currentTaskId) scope.currentTaskId = selfstarter.job.taskId;
|
|
337
333
|
if (event === "UserPromptSubmit") {
|
|
338
334
|
const prompt = promptFromInput(input);
|
|
@@ -372,14 +368,14 @@ async function runHookCore(input, payload, options) {
|
|
|
372
368
|
focusActive: true, includeSourceContent: event === "UserPromptSubmit" ? false : !scope.groupId,
|
|
373
369
|
maxBytes: event === "UserPromptSubmit" ? 4096 : scope.config.maxBriefingBytes,
|
|
374
370
|
now: input.timestamp || new Date(),
|
|
375
|
-
catalog, userStateRoot: resolvedSources.userStateRoot, sourceDiagnostics: event === "UserPromptSubmit" ? null :
|
|
371
|
+
catalog, userStateRoot: resolvedSources.userStateRoot, sourceDiagnostics: event === "UserPromptSubmit" ? null : diagnostics,
|
|
376
372
|
prompt: event === "UserPromptSubmit" ? promptFromInput(input) : null, preAnswer: event === "UserPromptSubmit"
|
|
377
373
|
});
|
|
378
374
|
if (event === "PostCompact") {
|
|
379
375
|
try { lessonRecall = await actionLessonRecall({ catalog, event, input, scope }); }
|
|
380
376
|
catch (error) { lessonRecall = { status: "degraded", items: [], reason: error.message, authority: "context-only" }; }
|
|
381
377
|
}
|
|
382
|
-
let context = renderContext(event, catalog, briefing, signal, attentionEvent, selfstarter, channelEvent,
|
|
378
|
+
let context = renderContext(event, catalog, briefing, signal, attentionEvent, selfstarter, channelEvent, diagnostics, preflight, lessonRecall);
|
|
383
379
|
if (event === "UserPromptSubmit" && Buffer.byteLength(context) > hostContextLimit(preflight)) {
|
|
384
380
|
throw new Error("mandatory preflight context exceeds the host hook injection limit");
|
|
385
381
|
}
|
|
@@ -388,7 +384,7 @@ async function runHookCore(input, payload, options) {
|
|
|
388
384
|
prompt: promptFromInput(input), now: input.timestamp || new Date() });
|
|
389
385
|
if (!finalized.preflightConsumed) throw new Error("preflight receipt could not be consumed atomically for this exact turn");
|
|
390
386
|
briefingOrigin = finalized.briefingOrigin;
|
|
391
|
-
|
|
387
|
+
diagnostics.timeline = finalized.timeline;
|
|
392
388
|
}
|
|
393
389
|
if (event === "UserPromptSubmit") {
|
|
394
390
|
const activeCanaries = briefing.learning.filter((item) => ["active", "revalidating"].includes(item.outcomeStatus));
|
|
@@ -420,17 +416,21 @@ async function runHookCore(input, payload, options) {
|
|
|
420
416
|
status: "degraded", receipts: [], reason: error.message, authority: "context-only"
|
|
421
417
|
};
|
|
422
418
|
}
|
|
423
|
-
const
|
|
424
|
-
|
|
419
|
+
const fitted = fitTimelineRecallToHostContext({ timeline: diagnostics.timeline,
|
|
420
|
+
maximumBytes: hostContextLimit(preflight), render: (timeline) => renderContext(event, catalog, briefing,
|
|
421
|
+
signal, attentionEvent, selfstarter, channelEvent, { ...diagnostics, timeline },
|
|
422
|
+
preflight, lessonRecall) });
|
|
423
|
+
diagnostics.timeline = fitted.timeline;
|
|
424
|
+
const enriched = fitted.context;
|
|
425
425
|
if (Buffer.byteLength(enriched) <= hostContextLimit(preflight)) context = enriched;
|
|
426
426
|
else if (preflight.learningApplications.status === "degraded") {
|
|
427
427
|
preflight.learningApplications = null;
|
|
428
428
|
context = renderContext(event, catalog, briefing, signal, attentionEvent, selfstarter, channelEvent,
|
|
429
|
-
|
|
429
|
+
diagnostics, preflight, lessonRecall);
|
|
430
430
|
}
|
|
431
431
|
}
|
|
432
432
|
if (payload) return { blocked: false, context, briefing, preflight, signal, attentionEvent, channelEvent, lessonRecall, catalogPath };
|
|
433
|
-
const output = hookOutput(event, context);
|
|
433
|
+
const output = hookOutput(event, context, process.env, sourceWarning);
|
|
434
434
|
const handoff = await writeContextOutput(output, process.env.BLUN_PLUGIN_ROOT ? "king"
|
|
435
435
|
: process.env.PLUGIN_ROOT ? "codex" : process.env.CLAUDE_PLUGIN_ROOT ? "claude" : scope.host);
|
|
436
436
|
if (event === "UserPromptSubmit" && briefingOrigin) {
|
|
@@ -452,7 +452,7 @@ async function runHookCore(input, payload, options) {
|
|
|
452
452
|
const context = JSON.stringify({
|
|
453
453
|
schema: "agentspine.hook-context/v1", event, loaded: false, failedClosed: true,
|
|
454
454
|
indexedSources: catalog.summary.total,
|
|
455
|
-
sourceResolution:
|
|
455
|
+
sourceResolution: diagnostics,
|
|
456
456
|
error: error.message,
|
|
457
457
|
instruction: "Do not claim AgentSpine recall succeeded. Continue with the current request under native host rules and run agentspine audit before using remembered context.",
|
|
458
458
|
authority: "context-only"
|
|
@@ -106,9 +106,12 @@ function feedbackCandidates(values) {
|
|
|
106
106
|
const fields = ["id", "text", "message", "at",
|
|
107
107
|
...["provider", "digest", "session"].filter((key) => !shared.includes(key))];
|
|
108
108
|
const expected = { id: "assertion:user-feedback-", message: "timeline-event:" };
|
|
109
|
-
const prefixes = fields.map((key) =>
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
const prefixes = fields.map((key) => {
|
|
110
|
+
const prefix = expected[key] || (key === "at"
|
|
111
|
+
&& values.every((item) => /^\d{4}-\d{2}-\d{2}T\d{2}:/.test(item[key]))
|
|
112
|
+
? values[0][key].slice(0, 14) : "");
|
|
113
|
+
return values.every((item) => item[key].startsWith(prefix)) ? prefix : "";
|
|
114
|
+
});
|
|
112
115
|
return { fields, common: Object.fromEntries(shared.map((key) => [key, values[0][key]])), prefixes,
|
|
113
116
|
rows: values.map((item) => fields.map((key, index) => item[key].slice(prefixes[index].length))) };
|
|
114
117
|
}
|