@wrongstack/core 0.309.0 → 0.310.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/dist/chronicle/index.js +113 -41
- package/dist/chronicle/metrics-ingest.d.ts +7 -0
- package/dist/chronicle/metrics-schema.d.ts +4 -1
- package/dist/chronicle/project-server.js +94 -38
- package/dist/chronicle/query.d.ts +1 -0
- package/dist/chronicle/sqlite-journal-schema.d.ts +2 -1
- package/dist/chronicle/types.d.ts +2 -0
- package/dist/{agent-status-helpers.d.ts → coordination/agent-status-helpers.d.ts} +1 -1
- package/dist/{agent-status-tracker.d.ts → coordination/agent-status-tracker.d.ts} +2 -2
- package/dist/{middleware → coordination}/collab-pause.d.ts +1 -1
- package/dist/coordination/director-kanban-queue-helpers.d.ts +1 -1
- package/dist/coordination/director.d.ts +7 -0
- package/dist/coordination/explore-companion.d.ts +9 -6
- package/dist/coordination/fleet-status-tool.d.ts +1 -1
- package/dist/coordination/index.d.ts +5 -1
- package/dist/coordination/index.js +2392 -405
- package/dist/coordination/kanban-dispatch-port.d.ts +30 -0
- package/dist/coordination/kanban-ops-port.d.ts +21 -0
- package/dist/coordination/mailbox-hooks.d.ts +1 -1
- package/dist/coordination/mailbox-project-server.js +14 -9
- package/dist/coordination/mutation-engine.d.ts +5 -3
- package/dist/core/context.d.ts +35 -44
- package/dist/core/conversation-state.d.ts +16 -52
- package/dist/core/index.js +92 -25
- package/dist/core/provider-runner.d.ts +2 -2
- package/dist/core/run-env.d.ts +7 -28
- package/dist/core/streaming-response-builder.d.ts +2 -2
- package/dist/execution/index.js +138 -162
- package/dist/goal/index.js +100 -22
- package/dist/hq/auth-store.d.ts +9 -0
- package/dist/hq/cost-bridge.d.ts +1 -1
- package/dist/hq/index.js +69 -7
- package/dist/index.d.ts +12 -5
- package/dist/index.js +21185 -26807
- package/dist/infrastructure/index.js +232 -135
- package/dist/infrastructure/provider-cache-ledger.d.ts +1 -1
- package/dist/infrastructure/token-counter.d.ts +5 -1
- package/dist/kernel/events/file-events.d.ts +6 -0
- package/dist/kernel/events/provider-events.d.ts +10 -7
- package/dist/kernel/events/session-events.d.ts +4 -4
- package/dist/kernel/events/tool-events.d.ts +12 -2
- package/dist/observability/index.js +1 -1
- package/dist/plugin/index.js +218 -47
- package/dist/prompts/index.js +360 -3
- package/dist/security/auto-approve-policy.d.ts +2 -2
- package/dist/security/index.d.ts +1 -0
- package/dist/security/index.js +224 -68
- package/dist/security/kanban-boundary.d.ts +1 -1
- package/dist/security/kanban-governance-port.d.ts +28 -0
- package/dist/security/permission-helpers.d.ts +11 -0
- package/dist/security/permission-policy.d.ts +10 -1
- package/dist/security/yolo-risk.d.ts +17 -0
- package/dist/session-catalog/index.js +34 -5
- package/dist/session-catalog/project-server.js +34 -5
- package/dist/session-catalog/protocol.d.ts +1 -1
- package/dist/session-catalog/registry.d.ts +1 -1
- package/dist/session-catalog/store-schema.d.ts +1 -1
- package/dist/session-catalog/store.d.ts +1 -1
- package/dist/skills/index.js +39 -6
- package/dist/storage/annotations-store.d.ts +1 -1
- package/dist/storage/board-store-port.d.ts +45 -0
- package/dist/storage/completed-work-checkpoint.d.ts +1 -1
- package/dist/storage/config-loader/types.d.ts +1 -1
- package/dist/storage/event-bus-port.d.ts +27 -0
- package/dist/storage/file-session-writer.d.ts +47 -5
- package/dist/storage/goal-coordination.d.ts +1 -1
- package/dist/storage/goal-store.d.ts +1 -1
- package/dist/storage/index.d.ts +3 -4
- package/dist/storage/index.js +1107 -1462
- package/dist/storage/plan-store.d.ts +1 -1
- package/dist/storage/queue-store.d.ts +1 -1
- package/dist/storage/replay-log-store.d.ts +1 -1
- package/dist/storage/session-recovery.d.ts +10 -0
- package/dist/storage/session-resume-validation.d.ts +13 -1
- package/dist/storage/session-store/events.d.ts +1 -1
- package/dist/storage/session-store/load-session-data.d.ts +1 -1
- package/dist/storage/session-store/rename-session.d.ts +1 -1
- package/dist/storage/session-store/resume-session.d.ts +3 -1
- package/dist/storage/session-store/session-store-index.d.ts +2 -1
- package/dist/storage/session-store/types.d.ts +1 -1
- package/dist/storage/session-store.d.ts +70 -0
- package/dist/storage/session-summary-tracker.d.ts +8 -0
- package/dist/storage/session-write-buffer.d.ts +31 -2
- package/dist/storage/task-store.d.ts +1 -1
- package/dist/storage/todos-checkpoint.d.ts +1 -1
- package/dist/storage/tool-audit-log.d.ts +1 -1
- package/dist/tasking/index.js +100 -22
- package/dist/tasking/task-tracker.d.ts +24 -1
- package/dist/types/compactor.d.ts +2 -2
- package/dist/types/context.d.ts +212 -0
- package/dist/types/conversation-state.d.ts +109 -0
- package/dist/types/error-handler.d.ts +2 -2
- package/dist/types/file-event-record.d.ts +6 -0
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.js +17 -1
- package/dist/types/permission.d.ts +3 -3
- package/dist/types/plugin.d.ts +3 -3
- package/dist/types/provider-runner.d.ts +2 -2
- package/dist/types/provider.d.ts +10 -0
- package/dist/types/run-env.d.ts +32 -0
- package/dist/types/session.d.ts +3 -0
- package/dist/types/slash-command.d.ts +2 -2
- package/dist/types/token-counter.d.ts +30 -1
- package/dist/types/tool-executor.d.ts +2 -2
- package/dist/types/tool.d.ts +20 -5
- package/dist/utils/context-breakdown.d.ts +2 -2
- package/dist/utils/context-evidence.d.ts +12 -12
- package/dist/utils/crash-shield.d.ts +9 -0
- package/dist/utils/heap-watchdog.js +11 -3
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.js +126 -160
- package/dist/utils/regex-guard.d.ts +7 -30
- package/dist/utils/terminal-sanitize.d.ts +41 -0
- package/dist/utils/todos-format.d.ts +1 -1
- package/dist/utils/tool-subject.d.ts +1 -1
- package/dist/utils/tree-kill.d.ts +2 -0
- package/dist/utils/tree-kill.js +1 -0
- package/instructions/agents/chaos-monkey.md +5 -1
- package/instructions/coordination/subagent-baseline.md +10 -0
- package/instructions/system-lite.md +2 -0
- package/instructions/system-pro.md +40 -0
- package/instructions/system.md +15 -0
- package/package.json +7 -10
- package/dist/defaults/index.d.ts +0 -69
- package/dist/defaults/index.js +0 -37755
- /package/dist/{fleet-notifier.d.ts → coordination/fleet-notifier.d.ts} +0 -0
- /package/dist/{session-registry-atomic-file.d.ts → session-catalog/session-registry-atomic-file.d.ts} +0 -0
- /package/dist/{session-registry-types.d.ts → session-catalog/session-registry-types.d.ts} +0 -0
- /package/dist/{session-registry.d.ts → session-catalog/session-registry.d.ts} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ConversationState } from '../core/conversation-state.js';
|
|
2
|
-
import type { EventBus } from '
|
|
2
|
+
import type { EventBus } from './event-bus-port.js';
|
|
3
3
|
/**
|
|
4
4
|
* Plan items are the strategic counterpart to todos. Where `ctx.todos`
|
|
5
5
|
* is the moment-to-moment task board the LLM mutates per-turn, a plan
|
|
@@ -43,6 +43,8 @@ export interface RecoveryPlan {
|
|
|
43
43
|
context: string | null;
|
|
44
44
|
}
|
|
45
45
|
export interface InterruptedToolDetail {
|
|
46
|
+
/** Tool_use id — lets callers synthesize a matching error tool_result. */
|
|
47
|
+
id?: string | undefined;
|
|
46
48
|
name: string;
|
|
47
49
|
argsSummary?: string | undefined;
|
|
48
50
|
ts?: string | undefined;
|
|
@@ -64,6 +66,14 @@ export declare class SessionRecovery {
|
|
|
64
66
|
private readonly dir;
|
|
65
67
|
private static readonly MAX_PENDING_EVENTS;
|
|
66
68
|
private static readonly MAX_PENDING_BYTES;
|
|
69
|
+
/**
|
|
70
|
+
* Build a recovery plan from ALREADY-LOADED events without touching disk.
|
|
71
|
+
* executeResumeSession uses this because load() has already paid for the
|
|
72
|
+
* transcript read; recover()'s file scan would duplicate it.
|
|
73
|
+
*/
|
|
74
|
+
static buildRecoveryPlan(events: readonly SessionEvent[],
|
|
75
|
+
/** Canonical session id — threaded through so RecoveryPlan stays complete. */
|
|
76
|
+
sessionId: string): RecoveryPlan;
|
|
67
77
|
/**
|
|
68
78
|
* Scan a session log and return a `StaleSession` if and only if the newest
|
|
69
79
|
* lifecycle boundary is an `in_flight_start` without a later
|
|
@@ -13,7 +13,19 @@ export declare function validateResumeFileObservations(events: readonly SessionE
|
|
|
13
13
|
* strips previous notices from the replayed conversation before appending the
|
|
14
14
|
* current ones; these prefixes are how it recognizes them.
|
|
15
15
|
*/
|
|
16
|
-
export declare const RESUME_NOTICE_HEADERS: readonly ['[SESSION RESUME FILE VALIDATION]', '[SESSION RESUME INTERRUPTED WORK]'];
|
|
16
|
+
export declare const RESUME_NOTICE_HEADERS: readonly ['[SESSION RESUME FILE VALIDATION]', '[SESSION RESUME INTERRUPTED WORK]', '[SESSION RESUME CRASH RECOVERY]'];
|
|
17
|
+
/**
|
|
18
|
+
* Build the ephemeral system message shown when the resumed session's newest
|
|
19
|
+
* lifecycle boundary was a dangling `in_flight_start` — i.e. the previous
|
|
20
|
+
* process died mid-iteration. Lists the interrupted tool calls that were
|
|
21
|
+
* stripped from the restored conversation (adjacency repair) so the model
|
|
22
|
+
* can decide whether to retry.
|
|
23
|
+
* Returns null when the session did not crash.
|
|
24
|
+
*/
|
|
25
|
+
export declare function formatCrashRecoveryNotice(interruptedTools: ReadonlyArray<{
|
|
26
|
+
name: string;
|
|
27
|
+
argsSummary?: string | undefined;
|
|
28
|
+
}>, lastContext: string | null): string | null;
|
|
17
29
|
/**
|
|
18
30
|
* True for a system message this module produced on an earlier resume.
|
|
19
31
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EventBus } from '
|
|
1
|
+
import type { EventBus } from '../event-bus-port.js';
|
|
2
2
|
export type SessionStoreReadOperation = 'load' | 'load_events_only' | 'list' | 'summary' | 'index_read';
|
|
3
3
|
export type SessionStoreWriteOperation = 'create' | 'resume' | 'rename' | 'append' | 'flush' | 'close' | 'index_append' | 'compact' | 'checkpoint';
|
|
4
4
|
export type SessionStoreOutcome = 'success' | 'failure';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EventBus } from '
|
|
1
|
+
import type { EventBus } from '../event-bus-port.js';
|
|
2
2
|
import type { SecretScrubber } from '../../types/secret-scrubber.js';
|
|
3
3
|
import type { SessionSummary } from '../../types/session.js';
|
|
4
4
|
export interface RenameSessionParams {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fsp from 'node:fs/promises';
|
|
2
|
-
import type { EventBus } from '
|
|
2
|
+
import type { EventBus } from '../event-bus-port.js';
|
|
3
3
|
import type { SecretScrubber } from '../../types/secret-scrubber.js';
|
|
4
4
|
import type { ResumedSession, SessionData, SessionEvent, SessionSummary } from '../../types/session.js';
|
|
5
5
|
import type { SessionCheckpointCas } from '../session-checkpoint-cas.js';
|
|
@@ -20,6 +20,8 @@ export interface ResumeSessionParams {
|
|
|
20
20
|
}>>;
|
|
21
21
|
persistCatalogSummary: (summary: SessionSummary) => Promise<void>;
|
|
22
22
|
logWarn: (msg: string, ctx?: Record<string, unknown>) => void;
|
|
23
|
+
/** Store root used to rescan the full transcript when load() dropped events. */
|
|
24
|
+
sessionsDir?: string | undefined;
|
|
23
25
|
}
|
|
24
26
|
export declare function executeResumeSession(params: ResumeSessionParams): Promise<ResumedSession>;
|
|
25
27
|
export declare function openSessionForAppend(file: string): Promise<fsp.FileHandle>;
|
|
@@ -7,7 +7,8 @@ export declare function appendToIndexStrict(dir: string, indexFile: string, summ
|
|
|
7
7
|
export declare function writeTombstone(dir: string, indexFile: string, id: string, invalidateShard: (id: string) => Promise<void>, onTombstone: () => void): Promise<void>;
|
|
8
8
|
export declare function readIndexFile(indexFile: string, currentCache: IndexCacheEntry | null): Promise<{
|
|
9
9
|
summaries: readonly SessionSummary[];
|
|
10
|
+
deletedIds: ReadonlySet<string>;
|
|
10
11
|
cache: IndexCacheEntry | null;
|
|
11
12
|
}>;
|
|
12
|
-
export declare function compactIndexInner(indexFile: string, entries: readonly SessionSummary[]): Promise<void>;
|
|
13
|
+
export declare function compactIndexInner(indexFile: string, entries: readonly SessionSummary[], deletedIds?: ReadonlySet<string>): Promise<void>;
|
|
13
14
|
//# sourceMappingURL=session-store-index.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EventBus } from '
|
|
1
|
+
import type { EventBus } from '../event-bus-port.js';
|
|
2
2
|
import type { Logger } from '../../types/logger.js';
|
|
3
3
|
import type { SecretScrubber } from '../../types/secret-scrubber.js';
|
|
4
4
|
import type { SessionData, SessionEvent, SessionSummary } from '../../types/session.js';
|
|
@@ -16,6 +16,28 @@ export declare class DefaultSessionStore implements SessionStore {
|
|
|
16
16
|
private readonly _loadCache;
|
|
17
17
|
private readonly loadCache;
|
|
18
18
|
private _indexCache;
|
|
19
|
+
/**
|
|
20
|
+
* Tombstoned ids — hidden even if their JSONL remains on disk.
|
|
21
|
+
* Convention: readIndex() REASSIGNS this set from the parsed index file
|
|
22
|
+
* MERGED with _manualTombstones; writeTombstone() adds in-place immediately
|
|
23
|
+
* so an incremental cache rebuild can never resurrect a just-deleted
|
|
24
|
+
* session.
|
|
25
|
+
*/
|
|
26
|
+
private _indexDeletedIds;
|
|
27
|
+
/**
|
|
28
|
+
* Tombstones added by THIS store between reads. Merged into every fresh
|
|
29
|
+
* snapshot so a read racing writeTombstone cannot drop an in-flight
|
|
30
|
+
* deletion; entries are pruned once the parsed index file itself carries
|
|
31
|
+
* them.
|
|
32
|
+
*/
|
|
33
|
+
private readonly _manualTombstones;
|
|
34
|
+
/**
|
|
35
|
+
* File-truth tombstones from the last readIndex() parse (EXCLUDES
|
|
36
|
+
* _manualTombstones additions). compactIndexInner persists THIS snapshot so
|
|
37
|
+
* concurrent writeTombstones that landed after the parse are not written
|
|
38
|
+
* prematurely — they persist through their own append path instead.
|
|
39
|
+
*/
|
|
40
|
+
private _indexFileDeletedIds;
|
|
19
41
|
private readonly shardManifestCache;
|
|
20
42
|
private static readonly LIST_SCAN_CONCURRENCY;
|
|
21
43
|
private indexAppendCount;
|
|
@@ -30,6 +52,17 @@ export declare class DefaultSessionStore implements SessionStore {
|
|
|
30
52
|
private shardKeyForSessionId;
|
|
31
53
|
private invalidateShardManifestBySessionId;
|
|
32
54
|
private ensureShardDir;
|
|
55
|
+
/**
|
|
56
|
+
* Create a fresh session writer.
|
|
57
|
+
*
|
|
58
|
+
* @threadSafety Failure-prone steps (manifest invalidation, sidecar
|
|
59
|
+
* removal, catalog upsert, the durable `{action:'create'}` index row)
|
|
60
|
+
* run BEFORE the truncating `'w'` open, so no rejection path can destroy
|
|
61
|
+
* prior bytes. Ordinary index summary rows never undelete a tombstone
|
|
62
|
+
* (the parser only honors `{action:'create'}`), so a swallowed create-row
|
|
63
|
+
* would leave a live writer whose id stays hidden forever — that append
|
|
64
|
+
* is therefore required, not best-effort.
|
|
65
|
+
*/
|
|
33
66
|
create(meta: Omit<SessionMetadata, 'startedAt'>): Promise<SessionWriter>;
|
|
34
67
|
fork(id: string, opts?: SessionForkOptions): Promise<ForkedSession>;
|
|
35
68
|
readRawEvents(id: string): Promise<SessionEvent[]>;
|
|
@@ -63,8 +96,45 @@ export declare class DefaultSessionStore implements SessionStore {
|
|
|
63
96
|
private persistCatalogSummary;
|
|
64
97
|
private writeTombstone;
|
|
65
98
|
private compactIndex;
|
|
99
|
+
/**
|
|
100
|
+
* Compact the local index in place.
|
|
101
|
+
*
|
|
102
|
+
* Contract carried into the shared compactIndexInner helper
|
|
103
|
+
* (session-store-index.ts): `entries` MUST already exclude tombstoned ids,
|
|
104
|
+
* and the deleted-set argument is persisted VERBATIM — neither the helper
|
|
105
|
+
* nor its callers may resurrect filtered rows or invent deletions.
|
|
106
|
+
* Locking: callers MUST already hold the indexFile lock (both do:
|
|
107
|
+
* compactIndex() below and the appendToIndexStrict compaction hook);
|
|
108
|
+
* readIndex() inside reads that same locked file, so no second lock may
|
|
109
|
+
* be taken here (non-reentrant → deadlock).
|
|
110
|
+
*
|
|
111
|
+
* That same lock is what makes the _indexFileDeletedIds snapshot safe to
|
|
112
|
+
* pass across the await below: writeTombstone() appends under the identical
|
|
113
|
+
* non-reentrant indexFile lock, so no tombstone can land between our
|
|
114
|
+
* readIndex() and the snapshot handed to the helper. Compaction would
|
|
115
|
+
* otherwise be racing a delete it cannot see.
|
|
116
|
+
*/
|
|
66
117
|
private compactIndexInner;
|
|
67
118
|
private readIndex;
|
|
119
|
+
/**
|
|
120
|
+
* Merge close-time index rows with directory-scan results, keyed by id.
|
|
121
|
+
* Scanned entries win — their metadata is re-derived from the transcript,
|
|
122
|
+
* so it reflects mid-session activity that index rows (written on close)
|
|
123
|
+
* cannot know about. Indexed-only ids fill gaps; duplicates within the
|
|
124
|
+
* index resolve last-wins, matching append order.
|
|
125
|
+
*/
|
|
126
|
+
private mergeIndexWithScan;
|
|
127
|
+
/**
|
|
128
|
+
* Rebuild the index from what is actually on disk.
|
|
129
|
+
*
|
|
130
|
+
* @returns the number of healthy, live entries in the rebuilt index. Both
|
|
131
|
+
* backends report that same quantity: ids whose summary could not be derived
|
|
132
|
+
* are excluded (the catalog counts them as `damaged`; the local scan drops
|
|
133
|
+
* them when `summaryFor` rejects), and ids carrying a surviving tombstone are
|
|
134
|
+
* excluded (the catalog rebuilds only from live files; the local branch skips
|
|
135
|
+
* them explicitly). It is NOT a count of rows written to the file — tombstone
|
|
136
|
+
* rows are persisted but never counted.
|
|
137
|
+
*/
|
|
68
138
|
rebuildIndex(): Promise<number>;
|
|
69
139
|
private listFromDirectoryScan;
|
|
70
140
|
private collectShardKeys;
|
|
@@ -30,6 +30,14 @@ export declare class SessionSummaryTracker {
|
|
|
30
30
|
private openToolUses;
|
|
31
31
|
constructor(opts: SessionSummaryTrackerOptions);
|
|
32
32
|
get currentSummary(): Readonly<SessionSummary>;
|
|
33
|
+
/**
|
|
34
|
+
* Non-destructive materialization of every live counter into a summary
|
|
35
|
+
* snapshot. Unlike finalize(), it stamps no endedAt, applies no final
|
|
36
|
+
* outcome, and resolves no name — mid-session metadata checkpoints use it
|
|
37
|
+
* so a killed process leaves accurate listing metadata behind without
|
|
38
|
+
* pretending the session ended cleanly.
|
|
39
|
+
*/
|
|
40
|
+
snapshot(): SessionSummary;
|
|
33
41
|
get pendingToolUses(): string[];
|
|
34
42
|
observe(event: SessionEvent): void;
|
|
35
43
|
recomputeFromDisk(filePath: string): Promise<void>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fsp from 'node:fs/promises';
|
|
2
|
-
import type { EventBus } from '
|
|
2
|
+
import type { EventBus } from './event-bus-port.js';
|
|
3
3
|
import type { SessionEvent } from '../types/session.js';
|
|
4
4
|
export interface SessionWriteBufferOptions {
|
|
5
5
|
sessionId: string;
|
|
@@ -9,6 +9,17 @@ export interface SessionWriteBufferOptions {
|
|
|
9
9
|
events?: EventBus | undefined;
|
|
10
10
|
getTraceId?: () => string | undefined;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Options for explicit (immediate-path) flushes.
|
|
14
|
+
*/
|
|
15
|
+
export interface FlushBufferOptions {
|
|
16
|
+
/**
|
|
17
|
+
* fsync-level durability after the append lands: disk-durable rather than
|
|
18
|
+
* only page-cache durable (SIGKILL survives page cache; power loss does
|
|
19
|
+
* not). Best-effort — a failed datasync never fails the flush itself.
|
|
20
|
+
*/
|
|
21
|
+
datasync?: boolean;
|
|
22
|
+
}
|
|
12
23
|
export declare class SessionWriteBuffer {
|
|
13
24
|
private readonly opts;
|
|
14
25
|
private static readonly FLUSH_INTERVAL_MS;
|
|
@@ -24,18 +35,36 @@ export declare class SessionWriteBuffer {
|
|
|
24
35
|
private lastAppendWarnAt;
|
|
25
36
|
private writeChain;
|
|
26
37
|
private flushPromise;
|
|
38
|
+
/**
|
|
39
|
+
* Batch currently inside enqueueWrite. `flushSync` may steal it if the
|
|
40
|
+
* async append has not started, so a dying process writes in-flight +
|
|
41
|
+
* remaining buffer as one ordered append instead of racing a second fd.
|
|
42
|
+
*/
|
|
43
|
+
private inFlight;
|
|
27
44
|
constructor(opts: SessionWriteBufferOptions);
|
|
28
45
|
get isClosed(): boolean;
|
|
29
46
|
get length(): number;
|
|
30
47
|
private eventBytes;
|
|
31
48
|
push(event: SessionEvent): boolean;
|
|
32
49
|
enqueueWrite(data: string): Promise<void>;
|
|
50
|
+
private enqueueFlight;
|
|
33
51
|
scheduleFlush(isClosed?: boolean): void;
|
|
34
52
|
cancelTimer(): void;
|
|
35
|
-
flushBuffer(isClosed?: boolean): Promise<void>;
|
|
53
|
+
flushBuffer(isClosed?: boolean, opts?: FlushBufferOptions): Promise<void>;
|
|
36
54
|
private flushBufferOnce;
|
|
37
55
|
drainWriteChain(): Promise<void>;
|
|
38
56
|
drainFlushPromise(): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Last-gasp synchronous append (SIGKILL/SIGTERM traps, `process.on('exit')`).
|
|
59
|
+
*
|
|
60
|
+
* Failure contract: nothing is discarded before the write is known to have
|
|
61
|
+
* landed. Buffered events stay in `writeBuffer` — it is cleared only after
|
|
62
|
+
* `fsyncSync` returns — and a stolen in-flight batch is handed back to the
|
|
63
|
+
* async write chain, so a survivable failure (EACCES, ENOSPC, EMFILE) loses
|
|
64
|
+
* nothing. Failure is never silent: a structured `session.flush_sync_failed`
|
|
65
|
+
* warning names exactly what was still pending, which is what SIGKILL-trap
|
|
66
|
+
* tests assert against.
|
|
67
|
+
*/
|
|
39
68
|
flushSync(): void;
|
|
40
69
|
clear(): void;
|
|
41
70
|
shouldFlushNow(): boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { TodoItem } from '../core/context.js';
|
|
2
2
|
import type { ConversationState } from '../core/conversation-state.js';
|
|
3
|
-
import type { EventBus } from '
|
|
3
|
+
import type { EventBus } from './event-bus-port.js';
|
|
4
4
|
/**
|
|
5
5
|
* On-disk checkpoint for `ctx.todos`. Written atomically every time the
|
|
6
6
|
* todo list changes, read once on session resume. This is the missing
|
package/dist/tasking/index.js
CHANGED
|
@@ -232,6 +232,13 @@ var TaskTracker = class _TaskTracker {
|
|
|
232
232
|
graph = null;
|
|
233
233
|
transitions = [];
|
|
234
234
|
listeners = [];
|
|
235
|
+
/**
|
|
236
|
+
* Re-entrancy guard for status cascades: true while an auto-block /
|
|
237
|
+
* auto-unblock cascade is running. Listener-driven mutations during a
|
|
238
|
+
* cascade still record their own primary transition — only their
|
|
239
|
+
* cascading is skipped (prevents unbounded recursion).
|
|
240
|
+
*/
|
|
241
|
+
cascading = false;
|
|
235
242
|
static MAX_TRANSITIONS = 1e4;
|
|
236
243
|
/**
|
|
237
244
|
* Subscribe to live task mutations (add / update / status change). Returns an
|
|
@@ -307,6 +314,28 @@ var TaskTracker = class _TaskTracker {
|
|
|
307
314
|
message: "No graph loaded",
|
|
308
315
|
code: ERROR_CODES.SDD_INVALID_STATE
|
|
309
316
|
});
|
|
317
|
+
if (!this.graph.nodes.has(from) || !this.graph.nodes.has(to)) {
|
|
318
|
+
throw new SddError({
|
|
319
|
+
message: `Cannot add ${type} edge ${from} -> ${to}: endpoint task is not in the graph`,
|
|
320
|
+
code: ERROR_CODES.SDD_NOT_READY,
|
|
321
|
+
context: { from, to, type }
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
if (from === to) {
|
|
325
|
+
throw new SddError({
|
|
326
|
+
message: `Cannot add ${type} edge: self-loop on task ${from}`,
|
|
327
|
+
code: ERROR_CODES.SDD_INVALID_STATE,
|
|
328
|
+
context: { nodeId: from, type }
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
if (this.graph.edges.some((e) => e.from === from && e.to === to && e.type === type)) return;
|
|
332
|
+
if (type === "depends_on" && this.dependsOnTransitively(from, to, /* @__PURE__ */ new Set())) {
|
|
333
|
+
throw new SddError({
|
|
334
|
+
message: `Cannot add depends_on edge ${from} -> ${to}: it would create a dependency cycle`,
|
|
335
|
+
code: ERROR_CODES.SDD_INVALID_STATE,
|
|
336
|
+
context: { from, to }
|
|
337
|
+
});
|
|
338
|
+
}
|
|
310
339
|
this.graph.edges.push({
|
|
311
340
|
id: crypto.randomUUID(),
|
|
312
341
|
from,
|
|
@@ -407,7 +436,7 @@ var TaskTracker = class _TaskTracker {
|
|
|
407
436
|
if (status === "in_progress") {
|
|
408
437
|
node.startedAt = now;
|
|
409
438
|
}
|
|
410
|
-
this.transitions.push({ from, to: status, timestamp: now, reason });
|
|
439
|
+
this.transitions.push({ nodeId: id, from, to: status, timestamp: now, reason });
|
|
411
440
|
if (this.transitions.length > _TaskTracker.MAX_TRANSITIONS) {
|
|
412
441
|
this.transitions.splice(0, this.transitions.length - _TaskTracker.MAX_TRANSITIONS);
|
|
413
442
|
}
|
|
@@ -423,7 +452,7 @@ var TaskTracker = class _TaskTracker {
|
|
|
423
452
|
type: "status_changed",
|
|
424
453
|
nodeId: id,
|
|
425
454
|
node,
|
|
426
|
-
transition: { from, to: status, timestamp: now, reason }
|
|
455
|
+
transition: { nodeId: id, from, to: status, timestamp: now, reason }
|
|
427
456
|
});
|
|
428
457
|
}
|
|
429
458
|
updateNode(id, patch) {
|
|
@@ -514,40 +543,89 @@ var TaskTracker = class _TaskTracker {
|
|
|
514
543
|
}
|
|
515
544
|
return computeTaskProgress(this.graph);
|
|
516
545
|
}
|
|
517
|
-
|
|
518
|
-
|
|
546
|
+
/**
|
|
547
|
+
* Recorded transitions in insertion order (oldest first — the array is
|
|
548
|
+
* trimmed from the front). With `taskId`, only that task's transitions are
|
|
549
|
+
* returned, still oldest-first; a consumer wanting newest-first reverses
|
|
550
|
+
* the result. Per-task filtering is possible because every TaskTransition
|
|
551
|
+
* carries `nodeId` (BIZ-005).
|
|
552
|
+
*/
|
|
553
|
+
getTransitions(taskId) {
|
|
554
|
+
return taskId === void 0 ? [...this.transitions] : this.transitions.filter((t) => t.nodeId === taskId);
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Record a cascade-driven status change (BIZ-003): mutate the node, push a
|
|
558
|
+
* TaskTransition, persist, and notify subscribers — the same effects an
|
|
559
|
+
* explicit updateNodeStatus produces — so the board projector's live
|
|
560
|
+
* snapshot and the transition log observe auto-block/auto-unblock.
|
|
561
|
+
*/
|
|
562
|
+
recordCascadeStatusChange(nodeId, node, to, reason) {
|
|
563
|
+
const from = node.status;
|
|
564
|
+
if (from === to) return;
|
|
565
|
+
const now = Date.now();
|
|
566
|
+
node.status = to;
|
|
567
|
+
node.updatedAt = now;
|
|
568
|
+
if (this.graph) this.graph.updatedAt = now;
|
|
569
|
+
this.transitions.push({ nodeId, from, to, timestamp: now, reason });
|
|
570
|
+
if (this.transitions.length > _TaskTracker.MAX_TRANSITIONS) {
|
|
571
|
+
this.transitions.splice(0, this.transitions.length - _TaskTracker.MAX_TRANSITIONS);
|
|
572
|
+
}
|
|
573
|
+
this.persist();
|
|
574
|
+
this.notifyChange({
|
|
575
|
+
type: "status_changed",
|
|
576
|
+
nodeId,
|
|
577
|
+
node,
|
|
578
|
+
transition: { nodeId, from, to, timestamp: now, reason }
|
|
579
|
+
});
|
|
519
580
|
}
|
|
520
581
|
unblockDependents(completedId) {
|
|
521
|
-
if (!this.graph) return;
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
const
|
|
525
|
-
|
|
582
|
+
if (!this.graph || this.cascading) return;
|
|
583
|
+
this.cascading = true;
|
|
584
|
+
try {
|
|
585
|
+
const dependents = this.getDependents(completedId);
|
|
586
|
+
for (const depId of dependents) {
|
|
587
|
+
const dep = this.graph.nodes.get(depId);
|
|
588
|
+
if (dep?.status !== "blocked") continue;
|
|
526
589
|
const remainingBlockers = this.getBlockers(depId);
|
|
527
590
|
const allUnblocked = remainingBlockers.every((id) => {
|
|
528
591
|
const blocker = this.graph?.nodes.get(id);
|
|
529
592
|
return blocker?.status === "completed" || blocker?.status === "failed";
|
|
530
593
|
});
|
|
531
594
|
if (allUnblocked) {
|
|
532
|
-
|
|
533
|
-
|
|
595
|
+
this.recordCascadeStatusChange(
|
|
596
|
+
depId,
|
|
597
|
+
dep,
|
|
598
|
+
"pending",
|
|
599
|
+
`auto-unblocked: blocker ${completedId} reached a terminal state`
|
|
600
|
+
);
|
|
534
601
|
}
|
|
535
602
|
}
|
|
603
|
+
} finally {
|
|
604
|
+
this.cascading = false;
|
|
536
605
|
}
|
|
537
606
|
}
|
|
538
607
|
checkAndBlockIfNeeded(taskId) {
|
|
539
|
-
if (!this.graph) return;
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
if (
|
|
548
|
-
node
|
|
549
|
-
|
|
608
|
+
if (!this.graph || this.cascading) return;
|
|
609
|
+
this.cascading = true;
|
|
610
|
+
try {
|
|
611
|
+
const blockers = this.getBlockers(taskId);
|
|
612
|
+
const incomplete = blockers.filter((id) => {
|
|
613
|
+
const blocker = this.graph?.nodes.get(id);
|
|
614
|
+
return blocker?.status !== "completed" && blocker?.status !== "failed";
|
|
615
|
+
});
|
|
616
|
+
if (incomplete.length > 0) {
|
|
617
|
+
const node = this.graph.nodes.get(taskId);
|
|
618
|
+
if (node) {
|
|
619
|
+
this.recordCascadeStatusChange(
|
|
620
|
+
taskId,
|
|
621
|
+
node,
|
|
622
|
+
"blocked",
|
|
623
|
+
`auto-blocked: incomplete blocker(s) ${incomplete.join(", ")}`
|
|
624
|
+
);
|
|
625
|
+
}
|
|
550
626
|
}
|
|
627
|
+
} finally {
|
|
628
|
+
this.cascading = false;
|
|
551
629
|
}
|
|
552
630
|
}
|
|
553
631
|
/**
|
|
@@ -20,6 +20,8 @@ export interface TaskTrackerOptions {
|
|
|
20
20
|
onPersistError?: ((err: unknown) => void) | undefined;
|
|
21
21
|
}
|
|
22
22
|
export interface TaskTransition {
|
|
23
|
+
/** The task the transition applies to — required for per-task filtering. */
|
|
24
|
+
nodeId: string;
|
|
23
25
|
from: TaskNode['status'];
|
|
24
26
|
to: TaskNode['status'];
|
|
25
27
|
timestamp: number;
|
|
@@ -39,6 +41,13 @@ export declare class TaskTracker {
|
|
|
39
41
|
private graph;
|
|
40
42
|
private transitions;
|
|
41
43
|
private listeners;
|
|
44
|
+
/**
|
|
45
|
+
* Re-entrancy guard for status cascades: true while an auto-block /
|
|
46
|
+
* auto-unblock cascade is running. Listener-driven mutations during a
|
|
47
|
+
* cascade still record their own primary transition — only their
|
|
48
|
+
* cascading is skipped (prevents unbounded recursion).
|
|
49
|
+
*/
|
|
50
|
+
private cascading;
|
|
42
51
|
private static readonly MAX_TRANSITIONS;
|
|
43
52
|
constructor(opts: TaskTrackerOptions);
|
|
44
53
|
/**
|
|
@@ -89,7 +98,21 @@ export declare class TaskTracker {
|
|
|
89
98
|
getBlockers(taskId: string): string[];
|
|
90
99
|
canStart(taskId: string): boolean;
|
|
91
100
|
getProgress(): TaskProgress;
|
|
92
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Recorded transitions in insertion order (oldest first — the array is
|
|
103
|
+
* trimmed from the front). With `taskId`, only that task's transitions are
|
|
104
|
+
* returned, still oldest-first; a consumer wanting newest-first reverses
|
|
105
|
+
* the result. Per-task filtering is possible because every TaskTransition
|
|
106
|
+
* carries `nodeId` (BIZ-005).
|
|
107
|
+
*/
|
|
108
|
+
getTransitions(taskId?: string): TaskTransition[];
|
|
109
|
+
/**
|
|
110
|
+
* Record a cascade-driven status change (BIZ-003): mutate the node, push a
|
|
111
|
+
* TaskTransition, persist, and notify subscribers — the same effects an
|
|
112
|
+
* explicit updateNodeStatus produces — so the board projector's live
|
|
113
|
+
* snapshot and the transition log observe auto-block/auto-unblock.
|
|
114
|
+
*/
|
|
115
|
+
private recordCascadeStatusChange;
|
|
93
116
|
private unblockDependents;
|
|
94
117
|
private checkAndBlockIfNeeded;
|
|
95
118
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AgentContext } from './context.js';
|
|
2
2
|
export interface CompactRepairReport {
|
|
3
3
|
removedToolUses: string[];
|
|
4
4
|
removedToolResults: string[];
|
|
@@ -58,7 +58,7 @@ export interface CompactReport {
|
|
|
58
58
|
} | undefined;
|
|
59
59
|
}
|
|
60
60
|
export interface Compactor {
|
|
61
|
-
compact(ctx:
|
|
61
|
+
compact(ctx: AgentContext, opts?: {
|
|
62
62
|
aggressive?: boolean | undefined;
|
|
63
63
|
}): Promise<CompactReport>;
|
|
64
64
|
}
|