@vascend/talos 0.1.4 → 0.1.6
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/LICENSE +22 -0
- package/README.md +37 -12
- package/dist/core/agent.d.ts +16 -3
- package/dist/core/agent.js +170 -70
- package/dist/core/background.d.ts +10 -3
- package/dist/core/background.js +55 -9
- package/dist/core/bootstrap.js +1 -0
- package/dist/core/compaction.js +1 -1
- package/dist/core/config-storage.d.ts +5 -0
- package/dist/core/config-storage.js +83 -0
- package/dist/core/config.d.ts +11 -2
- package/dist/core/config.js +24 -8
- package/dist/core/context-ledger.js +2 -1
- package/dist/core/context-policy.d.ts +1 -1
- package/dist/core/context-policy.js +10 -7
- package/dist/core/credentials.js +29 -30
- package/dist/core/effort.d.ts +1 -1
- package/dist/core/effort.js +5 -1
- package/dist/core/execution-guidance.d.ts +1 -1
- package/dist/core/execution-guidance.js +2 -0
- package/dist/core/file-coordination.d.ts +3 -0
- package/dist/core/file-coordination.js +28 -0
- package/dist/core/file-lease-store.d.ts +3 -0
- package/dist/core/file-lease-store.js +28 -12
- package/dist/core/fusion.d.ts +24 -0
- package/dist/core/fusion.js +80 -0
- package/dist/core/model-limits.d.ts +1 -0
- package/dist/core/model-limits.js +5 -1
- package/dist/core/permission-queue.js +15 -15
- package/dist/core/request-plan.js +4 -3
- package/dist/core/session-naming.d.ts +3 -0
- package/dist/core/session-naming.js +47 -0
- package/dist/core/session-usage.js +4 -1
- package/dist/core/session.d.ts +5 -2
- package/dist/core/session.js +22 -7
- package/dist/core/settings.d.ts +2 -0
- package/dist/core/settings.js +5 -16
- package/dist/core/step-policy.d.ts +18 -0
- package/dist/core/step-policy.js +6 -0
- package/dist/core/storage-health.d.ts +24 -0
- package/dist/core/storage-health.js +72 -0
- package/dist/core/tool-artifacts.d.ts +2 -0
- package/dist/core/tool-artifacts.js +21 -3
- package/dist/core/tool-result-index.js +29 -33
- package/dist/core/tool-result-store.js +6 -5
- package/dist/core/types.d.ts +26 -2
- package/dist/core/user-inbox.d.ts +9 -0
- package/dist/core/user-inbox.js +22 -3
- package/dist/core/user-input.js +1 -1
- package/dist/fusion/autopilot-fast.d.ts +59 -0
- package/dist/fusion/autopilot-fast.js +213 -0
- package/dist/fusion/autopilot.d.ts +75 -0
- package/dist/fusion/autopilot.js +308 -0
- package/dist/fusion/focus.d.ts +30 -0
- package/dist/fusion/focus.js +122 -0
- package/dist/fusion/gate.d.ts +30 -0
- package/dist/fusion/gate.js +95 -0
- package/dist/fusion/jev.d.ts +71 -0
- package/dist/fusion/jev.js +164 -0
- package/dist/fusion/receipts.d.ts +25 -0
- package/dist/fusion/receipts.js +34 -0
- package/dist/fusion/route.d.ts +25 -0
- package/dist/fusion/route.js +84 -0
- package/dist/fusion/run.d.ts +4 -0
- package/dist/fusion/run.js +186 -0
- package/dist/horizon/engine.d.ts +20 -1
- package/dist/horizon/engine.js +57 -0
- package/dist/horizon/index.d.ts +2 -2
- package/dist/horizon/index.js +1 -1
- package/dist/horizon/types.d.ts +29 -0
- package/dist/index.js +12 -0
- package/dist/providers/anthropic-oauth.d.ts +8 -3
- package/dist/providers/anthropic-oauth.js +13 -10
- package/dist/providers/anthropic.d.ts +9 -2
- package/dist/providers/anthropic.js +64 -21
- package/dist/providers/cli-models.d.ts +30 -0
- package/dist/providers/cli-models.js +194 -0
- package/dist/providers/model-effort.js +1 -1
- package/dist/providers/openai-oauth.d.ts +8 -3
- package/dist/providers/openai-oauth.js +9 -3
- package/dist/providers/openrouter-manual-fallback.d.ts +3 -1
- package/dist/providers/openrouter-manual-fallback.js +20 -20
- package/dist/providers/registry.js +3 -2
- package/dist/providers/retry.d.ts +3 -0
- package/dist/providers/retry.js +44 -1
- package/dist/remote/cifratura.d.ts +12 -0
- package/dist/remote/cifratura.js +66 -0
- package/dist/remote/index.d.ts +15 -0
- package/dist/remote/index.js +65 -0
- package/dist/remote/relay-client.d.ts +58 -0
- package/dist/remote/relay-client.js +315 -0
- package/dist/remote/sessione-remota.d.ts +60 -0
- package/dist/remote/sessione-remota.js +266 -0
- package/dist/sdk.d.ts +7 -2
- package/dist/sdk.js +3 -1
- package/dist/tools/builtin.js +41 -20
- package/dist/tools/capabilities.d.ts +3 -1
- package/dist/tools/capabilities.js +5 -2
- package/dist/tools/computer-use.js +2 -1
- package/dist/tools/pagination.js +1 -1
- package/dist/tools/program.js +34 -21
- package/dist/tools/team-tools.js +11 -6
- package/dist/tui/account-entry.js +14 -9
- package/dist/tui/agent-label.d.ts +2 -0
- package/dist/tui/agent-label.js +5 -0
- package/dist/tui/agent-sessions-screen.d.ts +35 -0
- package/dist/tui/agent-sessions-screen.js +178 -0
- package/dist/tui/agent-sessions.d.ts +53 -0
- package/dist/tui/agent-sessions.js +236 -0
- package/dist/tui/bottom-input.d.ts +57 -2
- package/dist/tui/bottom-input.js +449 -54
- package/dist/tui/branding.js +8 -4
- package/dist/tui/capability-lists.d.ts +10 -0
- package/dist/tui/capability-lists.js +41 -0
- package/dist/tui/command-output-registry.js +4 -0
- package/dist/tui/command-transcript.d.ts +1 -1
- package/dist/tui/command-transcript.js +17 -21
- package/dist/tui/commands.js +6 -2
- package/dist/tui/compaction-status.d.ts +7 -0
- package/dist/tui/compaction-status.js +39 -0
- package/dist/tui/config-recovery.d.ts +12 -0
- package/dist/tui/config-recovery.js +54 -0
- package/dist/tui/context-meter.d.ts +3 -0
- package/dist/tui/context-meter.js +35 -8
- package/dist/tui/diff.d.ts +9 -0
- package/dist/tui/diff.js +43 -19
- package/dist/tui/fusion-menu.d.ts +5 -0
- package/dist/tui/fusion-menu.js +89 -0
- package/dist/tui/i18n-catalog.js +351 -2
- package/dist/tui/live-command.d.ts +1 -0
- package/dist/tui/live-command.js +9 -0
- package/dist/tui/live-output.d.ts +1 -0
- package/dist/tui/live-output.js +11 -0
- package/dist/tui/logo.js +3 -0
- package/dist/tui/model-details.js +13 -12
- package/dist/tui/model-picker.d.ts +2 -0
- package/dist/tui/model-picker.js +59 -0
- package/dist/tui/model-selection.js +19 -10
- package/dist/tui/native-scrollback.d.ts +2 -1
- package/dist/tui/native-scrollback.js +31 -6
- package/dist/tui/prompt-queue-store.d.ts +1 -0
- package/dist/tui/prompt-queue-store.js +4 -0
- package/dist/tui/prompt-queue.d.ts +3 -0
- package/dist/tui/prompt-queue.js +19 -1
- package/dist/tui/provider-label.d.ts +2 -0
- package/dist/tui/provider-label.js +9 -0
- package/dist/tui/provider-menu.js +25 -24
- package/dist/tui/queue-screen.js +2 -1
- package/dist/tui/queued-questions.js +2 -4
- package/dist/tui/quick-help.d.ts +1 -1
- package/dist/tui/quick-help.js +3 -0
- package/dist/tui/remote-command.d.ts +7 -0
- package/dist/tui/remote-command.js +79 -0
- package/dist/tui/repl.d.ts +11 -2
- package/dist/tui/repl.js +457 -327
- package/dist/tui/review-store.d.ts +8 -1
- package/dist/tui/review-store.js +64 -11
- package/dist/tui/review-team.js +4 -3
- package/dist/tui/runtime-error.d.ts +2 -0
- package/dist/tui/runtime-error.js +26 -0
- package/dist/tui/scrollback-archive.d.ts +16 -0
- package/dist/tui/scrollback-archive.js +90 -0
- package/dist/tui/selector-mouse.d.ts +1 -0
- package/dist/tui/selector-mouse.js +1 -1
- package/dist/tui/selector.d.ts +11 -0
- package/dist/tui/selector.js +28 -4
- package/dist/tui/session-controls.d.ts +26 -0
- package/dist/tui/session-controls.js +65 -0
- package/dist/tui/session-naming-notice.d.ts +14 -0
- package/dist/tui/session-naming-notice.js +59 -0
- package/dist/tui/session-picker.d.ts +2 -0
- package/dist/tui/session-picker.js +32 -0
- package/dist/tui/session-replay.d.ts +1 -0
- package/dist/tui/session-replay.js +101 -32
- package/dist/tui/session-transcript-layout.d.ts +9 -0
- package/dist/tui/session-transcript-layout.js +41 -0
- package/dist/tui/startup.js +63 -33
- package/dist/tui/status-line.d.ts +1 -1
- package/dist/tui/status-line.js +20 -12
- package/dist/tui/storage-warning.d.ts +2 -0
- package/dist/tui/storage-warning.js +35 -0
- package/dist/tui/talos-art-data.d.ts +1 -0
- package/dist/tui/talos-art-data.js +2 -0
- package/dist/tui/talos-art.d.ts +1 -0
- package/dist/tui/talos-art.js +9 -0
- package/dist/tui/task-labels.d.ts +1 -0
- package/dist/tui/task-labels.js +8 -0
- package/dist/tui/terminal-text.d.ts +2 -1
- package/dist/tui/terminal-text.js +28 -2
- package/dist/tui/theme.js +2 -2
- package/dist/tui/tool-presentation.d.ts +3 -0
- package/dist/tui/tool-presentation.js +98 -0
- package/dist/tui/tool-style.js +4 -0
- package/dist/tui/transcript-store.d.ts +25 -0
- package/dist/tui/transcript-store.js +151 -0
- package/dist/tui/usage-footer.d.ts +1 -1
- package/dist/tui/usage-footer.js +3 -1
- package/dist/tui/vascend-id.d.ts +49 -0
- package/dist/tui/vascend-id.js +321 -0
- package/dist/tui/web-login-screen.js +4 -3
- package/dist/tui/web-login.d.ts +7 -15
- package/dist/tui/web-login.js +38 -78
- package/dist/tui/worker-transcripts.d.ts +31 -0
- package/dist/tui/worker-transcripts.js +91 -0
- package/dist/vascend/candidate-collector.d.ts +6 -5
- package/dist/vascend/candidate-collector.js +44 -43
- package/dist/vascend/context-feedback-loop.js +2 -0
- package/dist/vascend/context-lint.js +32 -2
- package/dist/vascend/context-market-selection.js +12 -5
- package/dist/vascend/context-pack-render.js +10 -4
- package/dist/vascend/context-packet-mirrors.d.ts +8 -0
- package/dist/vascend/context-packet-mirrors.js +24 -0
- package/dist/vascend/context-plan-events.d.ts +4 -0
- package/dist/vascend/context-plan-events.js +44 -0
- package/dist/vascend/context-snapshot.d.ts +1 -0
- package/dist/vascend/context-snapshot.js +8 -5
- package/dist/vascend/context-store-reader.d.ts +20 -0
- package/dist/vascend/context-store-reader.js +46 -0
- package/dist/vascend/context-window-compiler.d.ts +1 -0
- package/dist/vascend/context-window-compiler.js +1 -0
- package/dist/vascend/file-lock.d.ts +1 -0
- package/dist/vascend/file-lock.js +26 -0
- package/dist/vascend/granular-kingdom.d.ts +1 -0
- package/dist/vascend/granular-kingdom.js +52 -17
- package/dist/vascend/kingdom-bus.d.ts +1 -0
- package/dist/vascend/kingdom-bus.js +3 -0
- package/dist/vascend/kingdom-channel.d.ts +2 -0
- package/dist/vascend/kingdom-channel.js +3 -0
- package/dist/vascend/kingdom-live.d.ts +32 -4
- package/dist/vascend/kingdom-live.js +149 -13
- package/dist/vascend/kingdom-run.d.ts +5 -1
- package/dist/vascend/kingdom-run.js +6 -2
- package/dist/vascend/kingdom.d.ts +3 -0
- package/dist/vascend/kingdom.js +22 -11
- package/dist/vascend/memory-packet.d.ts +2 -1
- package/dist/vascend/memory-packet.js +31 -44
- package/dist/vascend/memory-store.d.ts +1 -0
- package/dist/vascend/memory-store.js +12 -4
- package/dist/vascend/plan-store.d.ts +3 -1
- package/dist/vascend/plan-store.js +6 -4
- package/dist/vascend/room-memory.d.ts +13 -2
- package/dist/vascend/room-memory.js +48 -6
- package/dist/vascend/runtime.d.ts +3 -11
- package/dist/vascend/runtime.js +11 -123
- package/dist/vascend/session-search.js +2 -0
- package/dist/vascend/team-session.js +3 -2
- package/dist/vascend/vision.d.ts +3 -1
- package/dist/vascend/vision.js +6 -4
- package/dist/vascend/worker-session.d.ts +20 -0
- package/dist/vascend/worker-session.js +83 -0
- package/package.json +2 -2
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type ContextCandidate } from "./context-candidate.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import type { RoomRecord } from "./room-memory.js";
|
|
3
|
+
import type { CastlePlan } from "./plan-store.js";
|
|
4
|
+
import type { Vision } from "./vision.js";
|
|
5
|
+
import type { DecisionEvent } from "./decision-promotion.js";
|
|
6
|
+
import { ContextStoreReader } from "./context-store-reader.js";
|
|
6
7
|
export interface CollectInput {
|
|
7
8
|
readonly sessionDir: string;
|
|
8
9
|
readonly scopeId: string;
|
|
@@ -13,7 +14,7 @@ export interface CollectInput {
|
|
|
13
14
|
/** Preselected dependencies supplied by a coherent snapshot. */
|
|
14
15
|
readonly projectScopes?: readonly string[];
|
|
15
16
|
}
|
|
16
|
-
export declare function collectCandidates(input: CollectInput): readonly ContextCandidate[];
|
|
17
|
+
export declare function collectCandidates(input: CollectInput, reader?: ContextStoreReader): readonly ContextCandidate[];
|
|
17
18
|
export declare function candidateFromRoom(r: RoomRecord, scopeId: string): ContextCandidate;
|
|
18
19
|
export declare function candidatesFromCastle(castle: CastlePlan, scopeId: string): readonly ContextCandidate[];
|
|
19
20
|
export declare function candidateFromVision(v: Vision, scopeId: string): ContextCandidate;
|
|
@@ -8,44 +8,44 @@
|
|
|
8
8
|
// Modulo di SOLA LETTURA: apre gli store e legge; nessuna scrittura. Robusto
|
|
9
9
|
// agli store mancanti (safe()).
|
|
10
10
|
import { candidateFromEvent, normalizeCandidate, } from "./context-candidate.js";
|
|
11
|
-
import {
|
|
12
|
-
import { RoomMemory } from "./room-memory.js";
|
|
13
|
-
import { VascendPlanStore } from "./plan-store.js";
|
|
14
|
-
import { VascendVisionStore } from "./vision.js";
|
|
15
|
-
import { DecisionStore } from "./decision-promotion.js";
|
|
11
|
+
import { listMemoryScopes } from "./memory-store.js";
|
|
16
12
|
import { listContextMemoryScopes } from "./context-fabric.js";
|
|
13
|
+
import { ContextStoreReader } from "./context-store-reader.js";
|
|
17
14
|
// Retrieval limits apply after ranking; explicit collector limits remain supported.
|
|
18
15
|
const DEFAULT_CANDIDATE_LIMIT = Number.POSITIVE_INFINITY;
|
|
19
16
|
// Raccoglie e de-duplica i candidati dal Memory OS. Deterministico a parita'
|
|
20
17
|
// di store su disco.
|
|
21
|
-
export function collectCandidates(input) {
|
|
18
|
+
export function collectCandidates(input, reader = new ContextStoreReader(input.sessionDir)) {
|
|
22
19
|
const limit = input.limit && input.limit > 0 ? input.limit : DEFAULT_CANDIDATE_LIMIT;
|
|
23
20
|
const out = new Map();
|
|
24
21
|
const add = (c) => {
|
|
25
22
|
if (!out.has(c.id))
|
|
26
23
|
out.set(c.id, c);
|
|
27
24
|
};
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
const localPlan = reader.plan(input.scopeId);
|
|
26
|
+
// History remains addressable, but the materialized plan owns live state.
|
|
27
|
+
for (const e of reader.events(input.scopeId)) {
|
|
30
28
|
add(candidateFromEvent(e, { scopeId: input.scopeId }));
|
|
31
29
|
}
|
|
32
30
|
// Le decisioni canoniche vivono in uno store dedicato. Indicizzarle solo
|
|
33
31
|
// come RoomRecord le degradava a kind=room e rendeva inattivi anchor,
|
|
34
32
|
// supersession e proof chain orientati alle decisioni.
|
|
35
|
-
for (const decision of
|
|
33
|
+
for (const decision of reader.decisions(input.scopeId)) {
|
|
36
34
|
add(candidateFromDecision(decision, input.scopeId));
|
|
37
35
|
}
|
|
38
36
|
// 2) Frontiera delle stanze (RoomRecord): indice vivo del castello.
|
|
39
|
-
for (const r of
|
|
37
|
+
for (const r of reader.rooms(input.scopeId)) {
|
|
40
38
|
add(candidateFromRoom(r, input.scopeId));
|
|
41
39
|
}
|
|
42
40
|
// 3) Castelli/task del piano: i task aperti sono spesso prerequisiti.
|
|
43
|
-
|
|
41
|
+
const plan = !input.planScopeId || input.planScopeId === input.scopeId
|
|
42
|
+
? localPlan : reader.plan(input.planScopeId);
|
|
43
|
+
for (const c of plan ?? []) {
|
|
44
44
|
for (const cand of candidatesFromCastle(c, input.planScopeId ?? input.scopeId))
|
|
45
45
|
add(cand);
|
|
46
46
|
}
|
|
47
47
|
// 4) Vision: stella polare, sempre dependency=true.
|
|
48
|
-
for (const v of
|
|
48
|
+
for (const v of reader.visions(input.scopeId)) {
|
|
49
49
|
add(candidateFromVision(v, input.scopeId));
|
|
50
50
|
}
|
|
51
51
|
// 5) Working set da sessioni recenti (cross-scope), peso ridotto: solo eventi.
|
|
@@ -60,22 +60,23 @@ export function collectCandidates(input) {
|
|
|
60
60
|
limit: allowedScopes ? 10_000 : projectScopeLimit,
|
|
61
61
|
}).filter((scope) => !allowedScopes || allowedScopes.has(scope)).slice(0, projectScopeLimit);
|
|
62
62
|
for (const scope of projectScopes) {
|
|
63
|
-
|
|
63
|
+
const scopePlan = reader.plan(scope);
|
|
64
|
+
for (const e of reader.events(scope).slice(input.projectScopes ? 0 : -12)) {
|
|
64
65
|
add(qualifyCrossScope(candidateFromEvent(e, { scopeId: scope }), scope));
|
|
65
66
|
}
|
|
66
|
-
for (const decision of
|
|
67
|
+
for (const decision of reader.decisions(scope).slice(input.projectScopes ? 0 : -6)) {
|
|
67
68
|
add(qualifyCrossScope(candidateFromDecision(decision, scope), scope));
|
|
68
69
|
}
|
|
70
|
+
if (scope !== input.planScopeId)
|
|
71
|
+
for (const castle of scopePlan ?? []) {
|
|
72
|
+
for (const candidate of candidatesFromCastle(castle, scope))
|
|
73
|
+
add(qualifyCrossScope(candidate, scope));
|
|
74
|
+
}
|
|
69
75
|
if (input.projectScopes) {
|
|
70
|
-
for (const room of
|
|
76
|
+
for (const room of reader.rooms(scope)) {
|
|
71
77
|
add(qualifyCrossScope(candidateFromRoom(room, scope), scope));
|
|
72
78
|
}
|
|
73
|
-
|
|
74
|
-
for (const castle of safe(() => VascendPlanStore.open(input.sessionDir, scope).castles(), [])) {
|
|
75
|
-
for (const candidate of candidatesFromCastle(castle, scope))
|
|
76
|
-
add(qualifyCrossScope(candidate, scope));
|
|
77
|
-
}
|
|
78
|
-
for (const vision of safe(() => VascendVisionStore.open(input.sessionDir, scope).all(), [])) {
|
|
79
|
+
for (const vision of reader.visions(scope)) {
|
|
79
80
|
add(qualifyCrossScope(candidateFromVision(vision, scope), scope));
|
|
80
81
|
}
|
|
81
82
|
}
|
|
@@ -100,20 +101,28 @@ export function candidateFromRoom(r, scopeId) {
|
|
|
100
101
|
});
|
|
101
102
|
}
|
|
102
103
|
export function candidatesFromCastle(castle, scopeId) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
104
|
+
const after = castle.after?.trim();
|
|
105
|
+
const dependency = after ? [normalizeCandidate({
|
|
106
|
+
id: `${castle.slug}/@after`, kind: "note", domain: "plan",
|
|
107
|
+
subject: castle.slug, summary: `@after[${castle.slug}]: ${after}`,
|
|
108
|
+
body: `Dipendenza corrente: ${castle.slug} dopo ${after}`,
|
|
109
|
+
terms: tokensOf(`${castle.slug} ${after} after dipendenza`),
|
|
110
|
+
links: [after], trust: "high", dependency: true, scopeId,
|
|
111
|
+
})] : [];
|
|
112
|
+
return [...dependency, ...castle.tasks.map((t) => normalizeCandidate({
|
|
113
|
+
id: `${castle.slug}/${t.id}`,
|
|
114
|
+
kind: "task",
|
|
115
|
+
domain: "plan",
|
|
116
|
+
subject: `${castle.title} :: ${t.title}`,
|
|
117
|
+
summary: `[${t.status}] ${t.title}`,
|
|
118
|
+
body: [t.title, ...(t.evidence ?? [])].join(" | "),
|
|
119
|
+
terms: tokensOf(`${castle.title} ${t.title} ${t.status} ${(t.evidence ?? []).join(" ")}`),
|
|
120
|
+
trust: "high",
|
|
121
|
+
// I task non completati sono prerequisiti per capire lo stato corrente.
|
|
122
|
+
dependency: t.status !== "completed",
|
|
123
|
+
// I task completati hanno freshness piena ma sono "fatti": status active.
|
|
124
|
+
scopeId,
|
|
125
|
+
}))];
|
|
117
126
|
}
|
|
118
127
|
export function candidateFromVision(v, scopeId) {
|
|
119
128
|
const features = Array.isArray(v.features) ? v.features.map((f) => featureLabel(f)).filter(Boolean) : [];
|
|
@@ -210,11 +219,3 @@ function capCandidates(candidates, limit) {
|
|
|
210
219
|
const keep = new Set(ranked.map(({ candidate }) => candidate.id));
|
|
211
220
|
return candidates.filter((candidate) => keep.has(candidate.id));
|
|
212
221
|
}
|
|
213
|
-
function safe(fn, fallback) {
|
|
214
|
-
try {
|
|
215
|
-
return fn();
|
|
216
|
-
}
|
|
217
|
-
catch {
|
|
218
|
-
return fallback;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
@@ -111,6 +111,8 @@ function snapshotFor(input) {
|
|
|
111
111
|
return input.snapshot ?? loadContextStoreSnapshot({
|
|
112
112
|
sessionDir: input.sessionDir,
|
|
113
113
|
scopeId: input.scopeId,
|
|
114
|
+
sharedProjectMemory: input.sharedProjectMemory,
|
|
115
|
+
...(input.planScopeId ? { planScopeId: input.planScopeId } : {}),
|
|
114
116
|
...(input.projectKey ? { projectKey: input.projectKey } : {}),
|
|
115
117
|
});
|
|
116
118
|
}
|
|
@@ -28,13 +28,33 @@ export function lintContext(input) {
|
|
|
28
28
|
}
|
|
29
29
|
// 2) DEDUP: stessa fonte/tipo e stesso contenuto. Summary uguali possono
|
|
30
30
|
// contenere prove diverse nel body; il codice preserva anche il case.
|
|
31
|
+
// room.closed mirrors use E* ids and subject=R*, while RoomRecord uses
|
|
32
|
+
// id=R* and subject=title. Only collapse a proven pair in the same scope;
|
|
33
|
+
// matching titles alone must not merge different rooms or stronger evidence.
|
|
34
|
+
const roomKeys = new Map();
|
|
35
|
+
for (const candidate of active) {
|
|
36
|
+
if (candidate.kind === "room" && candidate.domain === "rooms" && candidate.subject === candidate.summary) {
|
|
37
|
+
roomKeys.set(JSON.stringify([candidate.scopeId, localRoomId(candidate)]), roomContentKey(candidate, localRoomId(candidate)));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const linkedIds = new Set(active.flatMap((candidate) => candidate.links));
|
|
31
41
|
const seenId = new Set();
|
|
32
42
|
const seenContent = new Set();
|
|
33
43
|
const dedupDropped = [];
|
|
34
44
|
const cleaned = [];
|
|
35
45
|
for (const c of active) {
|
|
36
|
-
|
|
37
|
-
? JSON.stringify([c.kind, c.domain, c.subject, c.summary, c.body
|
|
46
|
+
let contentKey = c.subject || c.summary || c.body
|
|
47
|
+
? JSON.stringify([c.kind, c.domain, c.scopeId, c.subject, c.summary, c.body,
|
|
48
|
+
c.trust, c.dependency, [...c.links].sort(), [...c.terms].sort(), c.createdAt]) : "";
|
|
49
|
+
if (c.kind === "room" && c.domain === "rooms") {
|
|
50
|
+
const mirrorKey = roomContentKey(c, c.subject);
|
|
51
|
+
const canonicalKey = roomKeys.get(JSON.stringify([c.scopeId, c.subject]));
|
|
52
|
+
contentKey = canonicalKey === mirrorKey ? mirrorKey : roomContentKey(c, localRoomId(c));
|
|
53
|
+
}
|
|
54
|
+
// Every kind can be a graph target, not only rooms. Content deduplication
|
|
55
|
+
// must not leave a dangling reference or substitute weaker evidence.
|
|
56
|
+
if (linkedIds.has(c.id))
|
|
57
|
+
contentKey = "";
|
|
38
58
|
if (seenId.has(c.id) || (contentKey && seenContent.has(contentKey))) {
|
|
39
59
|
dedupDropped.push(c.id);
|
|
40
60
|
continue;
|
|
@@ -76,6 +96,16 @@ export function lintContext(input) {
|
|
|
76
96
|
const ok = !findings.some((f) => f.severity === "error");
|
|
77
97
|
return { findings, cleaned, ok };
|
|
78
98
|
}
|
|
99
|
+
function localRoomId(candidate) {
|
|
100
|
+
const prefix = candidate.scopeId ? `${candidate.scopeId}::` : "";
|
|
101
|
+
return prefix && candidate.id.startsWith(prefix) ? candidate.id.slice(prefix.length) : candidate.id;
|
|
102
|
+
}
|
|
103
|
+
function roomContentKey(candidate, roomId) {
|
|
104
|
+
return JSON.stringify([
|
|
105
|
+
"room", candidate.scopeId, roomId, candidate.summary, candidate.body,
|
|
106
|
+
candidate.trust, candidate.dependency, [...candidate.links].sort(),
|
|
107
|
+
]);
|
|
108
|
+
}
|
|
79
109
|
// Formatta il report del lint come blocco diagnostico (per log/debug, non per
|
|
80
110
|
// il prompt). Deterministico.
|
|
81
111
|
export function formatLintReport(report) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { capsuleFromCandidate, withExpansion } from "./context-capsule.js";
|
|
2
|
-
import {
|
|
3
|
-
import { estimateTokens } from "./budget-allocator.js";
|
|
2
|
+
import { renderCapsule } from "./expansion-levels.js";
|
|
4
3
|
import { runTokenMarket } from "./token-market.js";
|
|
5
4
|
export function selectWithMarket(input) {
|
|
6
5
|
const eligible = input.scored.filter((score) => score.tier !== "low");
|
|
@@ -18,6 +17,8 @@ export function selectWithMarket(input) {
|
|
|
18
17
|
});
|
|
19
18
|
const evidence = [];
|
|
20
19
|
const memory = [];
|
|
20
|
+
let evidenceChars = 0;
|
|
21
|
+
let memoryChars = 0;
|
|
21
22
|
const admitted = [];
|
|
22
23
|
const dropped = [...proposed.dropped];
|
|
23
24
|
for (const allocation of proposed.admitted) {
|
|
@@ -29,10 +30,16 @@ export function selectWithMarket(input) {
|
|
|
29
30
|
const capsule = withExpansion(allocation.capsule, expansion);
|
|
30
31
|
const zone = level >= 3 ? evidence : memory;
|
|
31
32
|
const budget = level >= 3 ? input.evidenceBudget : input.memoryBudget;
|
|
32
|
-
|
|
33
|
+
const chars = renderCapsule(capsule, expansion).length;
|
|
34
|
+
const nextChars = (level >= 3 ? evidenceChars : memoryChars) + (zone.length ? 2 : 0) + chars;
|
|
35
|
+
if (Math.ceil(nextChars / 4) > budget)
|
|
33
36
|
continue;
|
|
34
37
|
zone.push(capsule);
|
|
35
|
-
|
|
38
|
+
if (level >= 3)
|
|
39
|
+
evidenceChars = nextChars;
|
|
40
|
+
else
|
|
41
|
+
memoryChars = nextChars;
|
|
42
|
+
admitted.push({ ...allocation, capsule, level: expansion, tokensUsed: Math.max(1, Math.ceil(chars / 4)) });
|
|
36
43
|
placed = true;
|
|
37
44
|
break;
|
|
38
45
|
}
|
|
@@ -44,7 +51,7 @@ export function selectWithMarket(input) {
|
|
|
44
51
|
memory,
|
|
45
52
|
market: {
|
|
46
53
|
admitted, dropped, tokenBudget: proposed.tokenBudget,
|
|
47
|
-
tokensUsed:
|
|
54
|
+
tokensUsed: Math.ceil(evidenceChars / 4) + Math.ceil(memoryChars / 4),
|
|
48
55
|
},
|
|
49
56
|
};
|
|
50
57
|
}
|
|
@@ -3,19 +3,25 @@ export function packRendered(items, render, maxTokens, envelope = {}) {
|
|
|
3
3
|
const included = [];
|
|
4
4
|
const excluded = [];
|
|
5
5
|
const blocks = [];
|
|
6
|
-
|
|
6
|
+
const before = envelope.before?.trim() ?? "";
|
|
7
|
+
const after = envelope.after?.trim() ?? "";
|
|
8
|
+
let count = Number(Boolean(before)) + Number(Boolean(after));
|
|
9
|
+
let chars = before.length + after.length + (count > 1 ? 2 : 0);
|
|
7
10
|
for (const item of items) {
|
|
8
11
|
const block = render(item).trim();
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
// Count the exact joined length without rebuilding every accepted prefix.
|
|
13
|
+
const nextChars = chars + block.length + (count > 0 ? 2 : 0);
|
|
14
|
+
if (block && Math.ceil(nextChars / 4) <= maxTokens) {
|
|
11
15
|
blocks.push(block);
|
|
12
16
|
included.push(item);
|
|
13
|
-
|
|
17
|
+
chars = nextChars;
|
|
18
|
+
count += 1;
|
|
14
19
|
}
|
|
15
20
|
else {
|
|
16
21
|
excluded.push(item);
|
|
17
22
|
}
|
|
18
23
|
}
|
|
24
|
+
const text = included.length ? joinBlocks(before, ...blocks, after) : "";
|
|
19
25
|
return { text, included, excluded };
|
|
20
26
|
}
|
|
21
27
|
export function remainingTokens(budget, prefix) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MemoryEvent } from "./memory-store.js";
|
|
2
|
+
import type { RoomRecord } from "./room-memory.js";
|
|
3
|
+
interface Scoped<T> {
|
|
4
|
+
readonly scopeId: string;
|
|
5
|
+
readonly value: T;
|
|
6
|
+
}
|
|
7
|
+
export declare function packetEventsWithoutMirrors(events: readonly Scoped<MemoryEvent>[], rooms: readonly Scoped<RoomRecord>[]): readonly Scoped<MemoryEvent>[];
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
// Only omit exact generated mirrors of rooms actually rendered earlier in the
|
|
3
|
+
// packet. Unshown rooms, linked event IDs and additional metadata stay visible.
|
|
4
|
+
// Storage and retrieval identities are never changed by this rendering filter.
|
|
5
|
+
export function packetEventsWithoutMirrors(events, rooms) {
|
|
6
|
+
const key = (scope, id) => JSON.stringify([scope, id]);
|
|
7
|
+
const displayed = new Map(rooms.map(r => [key(r.scopeId, r.value.room), r.value]));
|
|
8
|
+
const linked = new Set();
|
|
9
|
+
for (const { scopeId, value } of events)
|
|
10
|
+
for (const link of value.links) {
|
|
11
|
+
const separator = link.target.indexOf("::");
|
|
12
|
+
linked.add(separator < 0 ? key(scopeId, link.target)
|
|
13
|
+
: key(link.target.slice(0, separator), link.target.slice(separator + 2)));
|
|
14
|
+
}
|
|
15
|
+
return events.filter(({ scopeId, value: e }) => {
|
|
16
|
+
if (e.domain !== "rooms" || !["room.closed", "rollup.created"].includes(e.type)
|
|
17
|
+
|| e.links.length || linked.has(key(scopeId, e.id)))
|
|
18
|
+
return true;
|
|
19
|
+
const room = displayed.get(key(scopeId, e.subject));
|
|
20
|
+
return !room || e.summary !== room.title || e.body !== room.body
|
|
21
|
+
|| e.createdAt !== room.createdAt || !isDeepStrictEqual(e.terms, room.terms)
|
|
22
|
+
|| !isDeepStrictEqual(e.payload, { room });
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { MemoryEvent } from "./memory-store.js";
|
|
2
|
+
import { type CastlePlan } from "./plan-store.js";
|
|
3
|
+
export declare function loadContextPlan(sessionDir: string, scopeId: string): readonly CastlePlan[] | null;
|
|
4
|
+
export declare function projectPlanEvents(events: readonly MemoryEvent[], currentPlan: readonly CastlePlan[] | null): readonly MemoryEvent[];
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { VascendPlanStore } from "./plan-store.js";
|
|
4
|
+
// Null means unavailable, not an authoritative empty plan. A corrupt snapshot
|
|
5
|
+
// must not make the only remaining evidence disappear from automatic context.
|
|
6
|
+
export function loadContextPlan(sessionDir, scopeId) {
|
|
7
|
+
if (!existsSync(join(sessionDir, `${scopeId}.castle.json`)))
|
|
8
|
+
return null;
|
|
9
|
+
try {
|
|
10
|
+
return VascendPlanStore.open(sessionDir, scopeId, { strict: true }).castles();
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const STATE_EVENTS = new Set([
|
|
17
|
+
"plan.castle.set", "plan.task.marked", "plan.task.evidence", "plan.tasks.added", "plan.after.set", "plan.cleared",
|
|
18
|
+
]);
|
|
19
|
+
// Read-only projection for automatic context, never for explicit recall.
|
|
20
|
+
// The materialized plan owns current state (also after replacement/removal).
|
|
21
|
+
// Event identities and complete historical evidence stay in the append-only log.
|
|
22
|
+
export function projectPlanEvents(events, currentPlan) {
|
|
23
|
+
let clearedAt = -1;
|
|
24
|
+
for (let i = 0; i < events.length; i++) {
|
|
25
|
+
if (events[i]?.domain === "plan" && events[i]?.type === "plan.cleared")
|
|
26
|
+
clearedAt = i;
|
|
27
|
+
}
|
|
28
|
+
return events.map((event, index) => {
|
|
29
|
+
if (event.domain !== "plan" || !STATE_EVENTS.has(event.type))
|
|
30
|
+
return event;
|
|
31
|
+
if (currentPlan !== null || index < clearedAt) {
|
|
32
|
+
return { ...event, payload: { ...event.payload, status: "archived" } };
|
|
33
|
+
}
|
|
34
|
+
if (event.type === "plan.cleared")
|
|
35
|
+
return event;
|
|
36
|
+
// Without a trustworthy snapshot these are historical observations, not
|
|
37
|
+
// claims about the live task state. Preserve their IDs for explicit recall.
|
|
38
|
+
return {
|
|
39
|
+
...event,
|
|
40
|
+
summary: `[cronologia; stato corrente non verificato] ${event.summary}`,
|
|
41
|
+
body: `[CRONOLOGIA_PIANO: stato corrente non verificato]\n${event.body}`,
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { readdirSync, statSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { collectCandidates } from "./candidate-collector.js";
|
|
5
|
+
import { ContextStoreReader } from "./context-store-reader.js";
|
|
5
6
|
import { buildMemoryPacket, clampMemoryPacket } from "./memory-packet.js";
|
|
6
7
|
import { retrieve } from "./context-retriever.js";
|
|
7
8
|
import { ContextSearchIndex } from "./context-search-index.js";
|
|
@@ -76,11 +77,12 @@ function legacyStoreSignature(sessionDir) {
|
|
|
76
77
|
}
|
|
77
78
|
export function loadContextStoreSnapshot(input) {
|
|
78
79
|
const indexed = input.indexed ?? process.env["TALOS_CONTEXT_INDEX"] !== "0";
|
|
79
|
-
const
|
|
80
|
-
const
|
|
80
|
+
const shared = input.sharedProjectMemory === true;
|
|
81
|
+
const key = JSON.stringify([input.sessionDir, input.scopeId, input.projectKey, input.planScopeId, indexed, shared]);
|
|
82
|
+
const projectScopes = !shared ? [] : indexed
|
|
81
83
|
? (input.projectKey ? projectMemoryScopes(input.sessionDir, input.projectKey) : []).filter((scope) => scope !== input.scopeId).sort()
|
|
82
84
|
: undefined;
|
|
83
|
-
const scopes = indexed ? [input.scopeId, ...(projectScopes ?? []), ...(input.planScopeId ? [input.planScopeId] : [])] : undefined;
|
|
85
|
+
const scopes = indexed || !shared ? [input.scopeId, ...(projectScopes ?? []), ...(input.planScopeId ? [input.planScopeId] : [])] : undefined;
|
|
84
86
|
const signature = storeSignature(input.sessionDir, scopes);
|
|
85
87
|
const previous = cache.get(key);
|
|
86
88
|
if (signature && previous?.signature === signature) {
|
|
@@ -104,8 +106,9 @@ export function loadContextStoreSnapshot(input) {
|
|
|
104
106
|
throw new Error("gli store di contesto stanno cambiando: snapshot coerente non disponibile");
|
|
105
107
|
}
|
|
106
108
|
function buildSnapshot(input, key, signature, projectScopes, indexed, previous) {
|
|
109
|
+
const reader = new ContextStoreReader(input.sessionDir);
|
|
107
110
|
const pool = collectCandidates({ ...input, ...(projectScopes ? { projectScopes } : {}),
|
|
108
|
-
...(indexed ? {} : { limit: 1000 }) });
|
|
111
|
+
...(indexed ? {} : { limit: 1000 }) }, reader);
|
|
109
112
|
const index = indexed ? new ContextSearchIndex(pool, previous?.index) : undefined;
|
|
110
113
|
// Eager capture: packet and candidates belong to the same checked revision.
|
|
111
114
|
const recent = new Map();
|
|
@@ -114,7 +117,7 @@ function buildSnapshot(input, key, signature, projectScopes, indexed, previous)
|
|
|
114
117
|
recent.set(candidate.scopeId, Math.max(recent.get(candidate.scopeId) ?? 0, candidate.createdAt));
|
|
115
118
|
const packetScopes = projectScopes?.slice().sort((a, b) => (recent.get(b) ?? 0) - (recent.get(a) ?? 0) || a.localeCompare(b)).slice(0, 6);
|
|
116
119
|
const packet = buildMemoryPacket({ ...input, maxChars: Number.MAX_SAFE_INTEGER,
|
|
117
|
-
...(packetScopes ? { projectScopes: packetScopes } : {}) });
|
|
120
|
+
...(packetScopes ? { projectScopes: packetScopes } : {}) }, reader);
|
|
118
121
|
const retrievals = new Map();
|
|
119
122
|
return {
|
|
120
123
|
key, signature, pool, ...(index ? { index } : {}),
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type MemoryEvent } from "./memory-store.js";
|
|
2
|
+
import { type RoomRecord } from "./room-memory.js";
|
|
3
|
+
import { type DecisionEvent } from "./decision-promotion.js";
|
|
4
|
+
import { type Vision } from "./vision.js";
|
|
5
|
+
import type { CastlePlan } from "./plan-store.js";
|
|
6
|
+
export declare class ContextStoreReader {
|
|
7
|
+
private readonly sessionDir;
|
|
8
|
+
private readonly plans;
|
|
9
|
+
private readonly eventViews;
|
|
10
|
+
private readonly frontiers;
|
|
11
|
+
private readonly visionViews;
|
|
12
|
+
private readonly decisionViews;
|
|
13
|
+
constructor(sessionDir: string);
|
|
14
|
+
plan(scope: string): readonly CastlePlan[] | null;
|
|
15
|
+
events(scope: string): readonly MemoryEvent[];
|
|
16
|
+
rooms(scope: string): readonly RoomRecord[];
|
|
17
|
+
visions(scope: string): readonly Vision[];
|
|
18
|
+
decisions(scope: string): readonly DecisionEvent[];
|
|
19
|
+
private once;
|
|
20
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { MemoryStore } from "./memory-store.js";
|
|
2
|
+
import { RoomMemory } from "./room-memory.js";
|
|
3
|
+
import { DecisionStore } from "./decision-promotion.js";
|
|
4
|
+
import { VascendVisionStore } from "./vision.js";
|
|
5
|
+
import { loadContextPlan, projectPlanEvents } from "./context-plan-events.js";
|
|
6
|
+
// One reader per snapshot attempt. Never reuse it across signature checks:
|
|
7
|
+
// collector and packet share immutable views, not a process-wide stale cache.
|
|
8
|
+
export class ContextStoreReader {
|
|
9
|
+
sessionDir;
|
|
10
|
+
plans = new Map();
|
|
11
|
+
eventViews = new Map();
|
|
12
|
+
frontiers = new Map();
|
|
13
|
+
visionViews = new Map();
|
|
14
|
+
decisionViews = new Map();
|
|
15
|
+
constructor(sessionDir) {
|
|
16
|
+
this.sessionDir = sessionDir;
|
|
17
|
+
}
|
|
18
|
+
plan(scope) {
|
|
19
|
+
return this.once(this.plans, scope, () => loadContextPlan(this.sessionDir, scope), null);
|
|
20
|
+
}
|
|
21
|
+
events(scope) {
|
|
22
|
+
return this.once(this.eventViews, scope, () => projectPlanEvents(MemoryStore.open(this.sessionDir, scope).all(), this.plan(scope)), []);
|
|
23
|
+
}
|
|
24
|
+
rooms(scope) {
|
|
25
|
+
return this.once(this.frontiers, scope, () => RoomMemory.open(this.sessionDir, scope).frontier(), []);
|
|
26
|
+
}
|
|
27
|
+
visions(scope) {
|
|
28
|
+
return this.once(this.visionViews, scope, () => VascendVisionStore.open(this.sessionDir, scope).all(), []);
|
|
29
|
+
}
|
|
30
|
+
decisions(scope) {
|
|
31
|
+
return this.once(this.decisionViews, scope, () => DecisionStore.open(this.sessionDir, scope).all(), []);
|
|
32
|
+
}
|
|
33
|
+
once(cache, scope, load, fallback) {
|
|
34
|
+
if (cache.has(scope))
|
|
35
|
+
return cache.get(scope);
|
|
36
|
+
let value;
|
|
37
|
+
try {
|
|
38
|
+
value = load();
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
value = fallback;
|
|
42
|
+
}
|
|
43
|
+
cache.set(scope, value);
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -12,6 +12,7 @@ export interface CompileInput {
|
|
|
12
12
|
readonly sessionDir: string;
|
|
13
13
|
readonly scopeId: string;
|
|
14
14
|
readonly projectKey?: string;
|
|
15
|
+
readonly sharedProjectMemory?: boolean;
|
|
15
16
|
readonly userText: string;
|
|
16
17
|
readonly retrievalQuery?: string;
|
|
17
18
|
readonly totalTokens?: number;
|
|
@@ -48,6 +48,7 @@ export function compileContext(input) {
|
|
|
48
48
|
const snapshot = input.snapshot ?? loadContextStoreSnapshot({
|
|
49
49
|
sessionDir: input.sessionDir,
|
|
50
50
|
scopeId: input.scopeId,
|
|
51
|
+
sharedProjectMemory: input.sharedProjectMemory,
|
|
51
52
|
...(input.planScopeId ? { planScopeId: input.planScopeId } : {}),
|
|
52
53
|
...(input.projectKey ? { projectKey: input.projectKey } : {}),
|
|
53
54
|
});
|
|
@@ -5,3 +5,4 @@ export interface FileLockOptions {
|
|
|
5
5
|
}
|
|
6
6
|
export declare function acquireLockSync(target: string, opts?: FileLockOptions): () => void;
|
|
7
7
|
export declare function withLockSync<T>(target: string, fn: () => T, opts?: FileLockOptions): T;
|
|
8
|
+
export declare function withLock<T>(target: string, fn: () => T | Promise<T>, opts?: FileLockOptions, signal?: AbortSignal): Promise<T>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
3
4
|
const DEFAULT_STALE_MS = 10_000;
|
|
4
5
|
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
5
6
|
const DEFAULT_RETRY_MS = 15;
|
|
@@ -93,3 +94,28 @@ export function withLockSync(target, fn, opts) {
|
|
|
93
94
|
release();
|
|
94
95
|
}
|
|
95
96
|
}
|
|
97
|
+
// Interactive callers must yield while another process owns the transaction.
|
|
98
|
+
// Each attempt uses the same atomic acquisition and stale-lock rules.
|
|
99
|
+
export async function withLock(target, fn, opts = {}, signal) {
|
|
100
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
101
|
+
const deadline = Date.now() + timeoutMs;
|
|
102
|
+
let release;
|
|
103
|
+
while (!release) {
|
|
104
|
+
signal?.throwIfAborted();
|
|
105
|
+
try {
|
|
106
|
+
release = acquireLockSync(target, { ...opts, timeoutMs: 0 });
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
if (Date.now() >= deadline)
|
|
110
|
+
throw error;
|
|
111
|
+
await delay(Math.min(Math.max(1, opts.retryMs ?? DEFAULT_RETRY_MS), deadline - Date.now()), undefined, { signal });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
signal?.throwIfAborted();
|
|
116
|
+
return await fn();
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
release();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { AgentEvent, ToolContext } from "../core/types.js";
|
|
|
3
3
|
import { type FileCoordinationContext } from "../core/file-coordination.js";
|
|
4
4
|
import { type TaskUnit, type TaskRunner, type GranularLaunchReport } from "./granular-launcher.js";
|
|
5
5
|
export interface GranularKingdomOptions {
|
|
6
|
+
readonly retainSessions?: boolean | "queued";
|
|
6
7
|
readonly harness: Harness;
|
|
7
8
|
readonly kingdomId: string;
|
|
8
9
|
readonly castleSlug: string;
|