negotium 0.6.13 → 0.6.17
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/agent-helpers.js +760 -142
- package/dist/agent-helpers.js.map +13 -12
- package/dist/{chunk-sr48tc2c.js → chunk-ax6tqx0s.js} +2 -2
- package/dist/{chunk-sr48tc2c.js.map → chunk-ax6tqx0s.js.map} +2 -2
- package/dist/hosted-agent.js +64 -3
- package/dist/hosted-agent.js.map +4 -4
- package/dist/main.js +783 -297
- package/dist/main.js.map +18 -17
- package/dist/mcp-factories.js +850 -261
- package/dist/mcp-factories.js.map +13 -12
- package/dist/registry.js +1 -1
- package/dist/runtime/cron/background-sessions.ts +5 -2
- package/dist/runtime/src/agents/archiver.ts +8 -5
- package/dist/runtime/src/agents/claude-provider.ts +88 -1
- package/dist/runtime/src/agents/idle-compact.ts +178 -0
- package/dist/runtime/src/node-host.ts +8 -1
- package/dist/runtime/src/runtime/background-sessions.ts +21 -8
- package/dist/runtime/src/runtime/turn-event-stream.ts +54 -1
- package/dist/runtime/src/runtime/turn-runner.ts +63 -0
- package/dist/runtime/src/topics/lifecycle.ts +2 -0
- package/dist/runtime/src/topics/session.ts +60 -3
- package/dist/runtime/src/version.ts +1 -1
- package/dist/types/packages/core/src/agents/archiver.d.ts +2 -2
- package/dist/types/packages/core/src/agents/idle-compact.d.ts +28 -0
- package/dist/types/packages/core/src/runtime/background-sessions.d.ts +10 -2
- package/dist/types/packages/core/src/runtime/turn-event-stream.d.ts +14 -0
- package/dist/types/packages/core/src/runtime/turn-runner.d.ts +2 -0
- package/dist/types/packages/core/src/topics/session.d.ts +11 -0
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/dist/types/packages/module-cron/src/background-sessions.d.ts +1 -1
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { rmSync } from "node:fs";
|
|
9
9
|
import { runArchiverTurn } from "#agents/archiver";
|
|
10
10
|
import { cancelIdleArchiveForTopic } from "#agents/idle-archiver";
|
|
11
|
+
import { cancelIdleCompactForTopic } from "#agents/idle-compact";
|
|
11
12
|
import { cancelSubagentWatchForDeletedTopic } from "#agents/mcp-tools/spawn-subagent";
|
|
12
13
|
import {
|
|
13
14
|
MIN_MEMORY_ARCHIVE_EXCHANGES,
|
|
@@ -59,6 +60,7 @@ async function abortAndWaitForTopic(topicId: string): Promise<boolean> {
|
|
|
59
60
|
// Drop first: the dying turn's finally block must not dispatch queued work.
|
|
60
61
|
interSessionQueue.drop(topicId);
|
|
61
62
|
cancelIdleArchiveForTopic(topicId);
|
|
63
|
+
cancelIdleCompactForTopic(topicId);
|
|
62
64
|
const aborted = abortRoom(topicId);
|
|
63
65
|
if (!aborted) return true;
|
|
64
66
|
|
|
@@ -6,6 +6,7 @@ import { tmpdir } from "node:os";
|
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { ACTIVE_TASK_TEMPLATE, estimateConversationTokens } from "#agents/compaction-support";
|
|
8
8
|
import { archiveActiveTopicForMemory, cancelIdleArchiveForTopic } from "#agents/idle-archiver";
|
|
9
|
+
import { cancelIdleCompactForTopic } from "#agents/idle-compact";
|
|
9
10
|
import { runAgent } from "#agents/index";
|
|
10
11
|
import { MIN_MEMORY_ARCHIVE_EXCHANGES } from "#agents/memory-archive-policy";
|
|
11
12
|
import { resolveCompactionExecution, resolveModelForAgent } from "#agents/model-catalog";
|
|
@@ -40,7 +41,10 @@ import {
|
|
|
40
41
|
beginRuntimeTopicMaintenance,
|
|
41
42
|
type RuntimeTopicMaintenanceHandle,
|
|
42
43
|
} from "#storage/runtime-topic-state";
|
|
43
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
cancelRuntimeUserTurnRequestsBeforeEpoch,
|
|
46
|
+
getRuntimeUserTurnRequest,
|
|
47
|
+
} from "#storage/runtime-turn-requests";
|
|
44
48
|
import { archiveConversationEvents } from "#storage/topic-archive";
|
|
45
49
|
import { isLegacySharedGeneral } from "#topics/personal-general";
|
|
46
50
|
import type { AgentKind, EffortLevel } from "#types";
|
|
@@ -62,6 +66,8 @@ export const AUTO_FORK_COMPACTION_TOKENS = 28_000;
|
|
|
62
66
|
export interface RestartTopicSessionResult {
|
|
63
67
|
text: string;
|
|
64
68
|
isError?: boolean;
|
|
69
|
+
/** True when a non-preemptive compact bailed because a turn was already in flight (see `preemptive`). */
|
|
70
|
+
busy?: boolean;
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
export interface CompactSummaryRequest {
|
|
@@ -79,6 +85,15 @@ export interface CompactSummaryRequest {
|
|
|
79
85
|
export interface CompactTopicSessionOptions {
|
|
80
86
|
summarize?: (request: CompactSummaryRequest) => Promise<string>;
|
|
81
87
|
cleanupOldRollouts?: typeof cleanupTopicRolloutsFromEntries;
|
|
88
|
+
/**
|
|
89
|
+
* When `false`, never interrupt work already in flight: instead of calling
|
|
90
|
+
* `fenceTopicWork` (which aborts local/remote turns and cancels queued
|
|
91
|
+
* requests), bail out as soon as maintenance reveals any turn is running or
|
|
92
|
+
* queued. Defaults to `true`, preserving `/compact`'s existing preemptive
|
|
93
|
+
* behavior — a human invoking it has implicitly accepted the interruption.
|
|
94
|
+
* Automatic/idle-triggered compaction must pass `false`.
|
|
95
|
+
*/
|
|
96
|
+
preemptive?: boolean;
|
|
82
97
|
}
|
|
83
98
|
|
|
84
99
|
export interface CompactedRolloutRequest extends Omit<CompactSummaryRequest, "source"> {
|
|
@@ -135,6 +150,27 @@ async function fenceTopicWork(
|
|
|
135
150
|
return maintenance.isOwned() ? null : "Topic maintenance ownership was lost. Try again.";
|
|
136
151
|
}
|
|
137
152
|
|
|
153
|
+
/** True when any turn is running (this process or a remote node) or queued for this topic. */
|
|
154
|
+
function hasTopicWorkInFlight(topicId: string): boolean {
|
|
155
|
+
return Boolean(
|
|
156
|
+
getRoomQuery(topicId) || getRuntimeTurnLease(topicId) || getRuntimeUserTurnRequest(topicId),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Non-preemptive counterpart to `fenceTopicWork`: never abort or cancel
|
|
162
|
+
* anything. `beginRuntimeTopicMaintenance` unconditionally bumps the topic's
|
|
163
|
+
* epoch just by being called, so callers must check *before* acquiring it
|
|
164
|
+
* (skip the bump entirely in the common busy case) and check again
|
|
165
|
+
* immediately *after* acquiring it (the authoritative read — once maintenance
|
|
166
|
+
* is held, `claimRuntimeTurnLease`'s own `WHERE NOT EXISTS (...maintenance)`
|
|
167
|
+
* guarantees no *new* turn can start, so anything found here was already
|
|
168
|
+
* running before we got here). Returns `true` when it is safe to proceed.
|
|
169
|
+
*/
|
|
170
|
+
function topicIsQuiescedForNonPreemptiveWork(topicId: string): boolean {
|
|
171
|
+
return !hasTopicWorkInFlight(topicId);
|
|
172
|
+
}
|
|
173
|
+
|
|
138
174
|
/**
|
|
139
175
|
* Reset provider-native and provider-neutral context without deleting the
|
|
140
176
|
* topic or its visible message history. Mirrors Otium's `/new` contract.
|
|
@@ -164,6 +200,7 @@ export async function restartTopicSession(
|
|
|
164
200
|
const fenceError = await fenceTopicWork(topicId, maintenance);
|
|
165
201
|
if (fenceError) return { text: fenceError, isError: true };
|
|
166
202
|
cancelIdleArchiveForTopic(topicId);
|
|
203
|
+
cancelIdleCompactForTopic(topicId);
|
|
167
204
|
const rawArchivePaths: string[] = [];
|
|
168
205
|
try {
|
|
169
206
|
for (const participantUserId of new Set([
|
|
@@ -731,12 +768,32 @@ export async function compactTopicSession(
|
|
|
731
768
|
);
|
|
732
769
|
if (!owner) return { text: "Only the topic owner can compact the session.", isError: true };
|
|
733
770
|
|
|
771
|
+
const preemptive = options.preemptive ?? true;
|
|
772
|
+
if (!preemptive && hasTopicWorkInFlight(topicId)) {
|
|
773
|
+
// Fast path: skip acquiring maintenance at all (which would otherwise
|
|
774
|
+
// unconditionally bump the topic epoch) in the common busy case.
|
|
775
|
+
return { text: "A turn is active or queued; compaction skipped.", isError: true, busy: true };
|
|
776
|
+
}
|
|
777
|
+
|
|
734
778
|
const maintenance = beginRuntimeTopicMaintenance(topicId);
|
|
735
779
|
if (!maintenance) return { text: "Topic maintenance is already in progress.", isError: true };
|
|
736
780
|
|
|
737
781
|
try {
|
|
738
|
-
|
|
739
|
-
|
|
782
|
+
if (preemptive) {
|
|
783
|
+
const fenceError = await fenceTopicWork(topicId, maintenance);
|
|
784
|
+
if (fenceError) return { text: fenceError, isError: true };
|
|
785
|
+
} else if (!topicIsQuiescedForNonPreemptiveWork(topicId)) {
|
|
786
|
+
// Authoritative check, now that no *new* turn can start under this
|
|
787
|
+
// epoch. Never abort or cancel anything already running — bail instead.
|
|
788
|
+
return {
|
|
789
|
+
text: "A turn is active or queued; compaction skipped.",
|
|
790
|
+
isError: true,
|
|
791
|
+
busy: true,
|
|
792
|
+
};
|
|
793
|
+
} else if (!maintenance.isOwned()) {
|
|
794
|
+
return { text: "Topic maintenance ownership was lost. Try again.", isError: true };
|
|
795
|
+
}
|
|
796
|
+
cancelIdleCompactForTopic(topicId);
|
|
740
797
|
|
|
741
798
|
const agent = (topic.agent ?? "maestro") as AgentKind;
|
|
742
799
|
const registry = getRegistry(agent);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const NEGOTIUM_VERSION = "0.6.
|
|
1
|
+
export const NEGOTIUM_VERSION = "0.6.17";
|
|
@@ -73,7 +73,7 @@ export interface ArchiverHost {
|
|
|
73
73
|
}
|
|
74
74
|
export interface ArchiverRuntime {
|
|
75
75
|
runArchiverTurn(params: RunArchiverTurnParams): boolean;
|
|
76
|
-
listActiveMemoryArchiverSessions(userId: string): BackgroundSessionDto[];
|
|
76
|
+
listActiveMemoryArchiverSessions(userId: string, allUsers?: boolean): BackgroundSessionDto[];
|
|
77
77
|
}
|
|
78
78
|
export declare function createArchiverRuntime(host: ArchiverHost): ArchiverRuntime;
|
|
79
79
|
/**
|
|
@@ -82,4 +82,4 @@ export declare function createArchiverRuntime(host: ArchiverHost): ArchiverRunti
|
|
|
82
82
|
*/
|
|
83
83
|
export declare function findSummaryFile(storage: ArchiverStorageHost, topicTitle: string, date: string, sinceMs: number, topicId?: string): string | null;
|
|
84
84
|
export declare function runArchiverTurn(params: RunArchiverTurnParams): boolean;
|
|
85
|
-
export declare function listActiveMemoryArchiverSessions(userId: string): BackgroundSessionDto[];
|
|
85
|
+
export declare function listActiveMemoryArchiverSessions(userId: string, allUsers?: boolean): BackgroundSessionDto[];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getTopicStats } from "../storage/token-stats";
|
|
2
|
+
import type { RestartTopicSessionResult } from "../topics/session";
|
|
3
|
+
type IdleCompactStatus = "scheduled" | "disabled" | "busy" | "topic-not-found" | "not-ai-invited" | "mention-only-channel" | "no-owner" | "below-threshold" | "compacted" | "failed";
|
|
4
|
+
export interface IdleCompactOptions {
|
|
5
|
+
isBusy?: (topicId: string) => boolean;
|
|
6
|
+
onBusy?: (topicId: string, userId: string) => void;
|
|
7
|
+
minContextPercent?: number;
|
|
8
|
+
/** Injected for tests; defaults to the real `getTopicStats`. */
|
|
9
|
+
getStats?: typeof getTopicStats;
|
|
10
|
+
/** Injected for tests; defaults to the real `compactTopicSession`. */
|
|
11
|
+
compact?: (topicId: string, userId: string, reason: string) => Promise<RestartTopicSessionResult>;
|
|
12
|
+
}
|
|
13
|
+
/** Cancel a pending idle-compact timer, e.g. before a topic is reset or deleted. */
|
|
14
|
+
export declare function cancelIdleCompactForTopic(topicId: string): boolean;
|
|
15
|
+
export declare function idleCompactDelayMs(): number;
|
|
16
|
+
/** Percentage (0–100) of the provider's context window an idle topic must reach before it is auto-compacted. */
|
|
17
|
+
export declare function idleCompactMinContextPercent(): number;
|
|
18
|
+
export declare function idleCompactEnabled(): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Schedule a check, `idleCompactDelayMs()` after the topic's last turn, that
|
|
21
|
+
* compacts the session if (and only if) it is still idle and its context has
|
|
22
|
+
* grown past the percent-of-window threshold. Runs alongside the memory
|
|
23
|
+
* idle-archiver — this only trims what the model sees next turn; it does not
|
|
24
|
+
* touch memory.
|
|
25
|
+
*/
|
|
26
|
+
export declare function scheduleIdleCompactForTopic(topicId: string, userId: string): IdleCompactStatus;
|
|
27
|
+
export declare function runIdleCompactForTopic(topicId: string, userId: string, options?: IdleCompactOptions): Promise<IdleCompactStatus>;
|
|
28
|
+
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BackgroundSessionDto } from "../types/api";
|
|
2
|
-
export type BackgroundSessionProvider = (userId: string) => BackgroundSessionDto[];
|
|
2
|
+
export type BackgroundSessionProvider = (userId: string, allUsers?: boolean) => BackgroundSessionDto[];
|
|
3
3
|
export interface TransientBackgroundSessionHandle {
|
|
4
4
|
id: string;
|
|
5
5
|
update(status: string, step?: string): void;
|
|
@@ -16,4 +16,12 @@ export declare function backgroundSessionProgress(topicId: string, queryId: stri
|
|
|
16
16
|
status: string;
|
|
17
17
|
steps: string[];
|
|
18
18
|
};
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* `allUsers` widens every section — memory archiver, compact/transient, and
|
|
21
|
+
* cron — past the caller's own participation. Meant for single-owner
|
|
22
|
+
* surfaces (terminal, Telegram) where "my view" and "everything on this
|
|
23
|
+
* node" are the same person anyway, so idle background upkeep (and compact,
|
|
24
|
+
* manual or idle-triggered) triggered under a different local userId is
|
|
25
|
+
* still visible.
|
|
26
|
+
*/
|
|
27
|
+
export declare function listBackgroundSessionsForUser(userId: string, allUsers?: boolean): BackgroundSessionDto[];
|
|
@@ -16,6 +16,20 @@ export type StreamAgentOutcome = {
|
|
|
16
16
|
} | {
|
|
17
17
|
kind: "provider-error";
|
|
18
18
|
error: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The provider's terminal result carried no assistant text at all — the
|
|
22
|
+
* "silent turn" bug: a session-level race (e.g. a queued background-task
|
|
23
|
+
* notification flushing into the CLI session at the same instant a new
|
|
24
|
+
* user turn is dispatched) makes the underlying CLI self-interrupt within
|
|
25
|
+
* ~1-2s and report a trivial zero-usage "success". Distinct from
|
|
26
|
+
* provider-error so the caller can retry once before surfacing a failure —
|
|
27
|
+
* a manual resend has reliably produced a normal reply in every observed
|
|
28
|
+
* case.
|
|
29
|
+
*/
|
|
30
|
+
| {
|
|
31
|
+
kind: "empty-response";
|
|
32
|
+
error: string;
|
|
19
33
|
};
|
|
20
34
|
export interface TurnEventStreamHooks {
|
|
21
35
|
appendSystemMessage: (topicId: string, text: string) => MessageDto;
|
|
@@ -197,6 +197,8 @@ export interface StartAiTurnParams extends AiTurnExecutionOptions {
|
|
|
197
197
|
/** Durable rows whose envelopes are already present in _userMessages. */
|
|
198
198
|
_durableRequestIds?: string[];
|
|
199
199
|
_sessionRetried?: boolean;
|
|
200
|
+
/** Internal only. Caps the empty-provider-response retry to a single attempt. */
|
|
201
|
+
_emptyResponseRetried?: boolean;
|
|
200
202
|
}
|
|
201
203
|
export interface TriggerTopicAiTurnOptions extends AiTurnExecutionOptions {
|
|
202
204
|
/** Run the injected turn without writing the inject prompt as a visible message. */
|
|
@@ -8,6 +8,8 @@ export declare const AUTO_FORK_COMPACTION_TOKENS = 28000;
|
|
|
8
8
|
export interface RestartTopicSessionResult {
|
|
9
9
|
text: string;
|
|
10
10
|
isError?: boolean;
|
|
11
|
+
/** True when a non-preemptive compact bailed because a turn was already in flight (see `preemptive`). */
|
|
12
|
+
busy?: boolean;
|
|
11
13
|
}
|
|
12
14
|
export interface CompactSummaryRequest {
|
|
13
15
|
topicId: string;
|
|
@@ -23,6 +25,15 @@ export interface CompactSummaryRequest {
|
|
|
23
25
|
export interface CompactTopicSessionOptions {
|
|
24
26
|
summarize?: (request: CompactSummaryRequest) => Promise<string>;
|
|
25
27
|
cleanupOldRollouts?: typeof cleanupTopicRolloutsFromEntries;
|
|
28
|
+
/**
|
|
29
|
+
* When `false`, never interrupt work already in flight: instead of calling
|
|
30
|
+
* `fenceTopicWork` (which aborts local/remote turns and cancels queued
|
|
31
|
+
* requests), bail out as soon as maintenance reveals any turn is running or
|
|
32
|
+
* queued. Defaults to `true`, preserving `/compact`'s existing preemptive
|
|
33
|
+
* behavior — a human invoking it has implicitly accepted the interruption.
|
|
34
|
+
* Automatic/idle-triggered compaction must pass `false`.
|
|
35
|
+
*/
|
|
36
|
+
preemptive?: boolean;
|
|
26
37
|
}
|
|
27
38
|
export interface CompactedRolloutRequest extends Omit<CompactSummaryRequest, "source"> {
|
|
28
39
|
entries: ConversationEntry[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const NEGOTIUM_VERSION = "0.6.
|
|
1
|
+
export declare const NEGOTIUM_VERSION = "0.6.17";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { type BackgroundSessionDto } from "@negotium/core/cron-host";
|
|
2
|
-
export declare function listCronBackgroundSessions(userId: string): BackgroundSessionDto[];
|
|
2
|
+
export declare function listCronBackgroundSessions(userId: string, allUsers?: boolean): BackgroundSessionDto[];
|