negotium 0.22.2 → 0.22.3
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 +2 -2
- package/dist/agent-helpers.js.map +2 -2
- package/dist/{chunk-gzph93xn.js → chunk-6c637pwe.js} +2 -2
- package/dist/{chunk-gzph93xn.js.map → chunk-6c637pwe.js.map} +2 -2
- package/dist/hosted-agent.js +2 -2
- package/dist/hosted-agent.js.map +2 -2
- package/dist/main.js +9 -126
- package/dist/main.js.map +4 -4
- package/dist/mcp-factories.js +2 -2
- package/dist/mcp-factories.js.map +2 -2
- package/dist/registry.js +1 -1
- package/dist/runtime/src/runtime/inbox.ts +33 -171
- package/dist/runtime/src/version.ts +1 -1
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/registry.js
CHANGED
|
@@ -132,7 +132,7 @@ var codexRegistryOperations = {
|
|
|
132
132
|
return { sessionId: threadId, rolloutPath };
|
|
133
133
|
},
|
|
134
134
|
async forkSession({ parentSessionId }) {
|
|
135
|
-
const { forkCodexSession } = await import("./chunk-
|
|
135
|
+
const { forkCodexSession } = await import("./chunk-6c637pwe.js");
|
|
136
136
|
return await forkCodexSession(parentSessionId);
|
|
137
137
|
},
|
|
138
138
|
async cleanupRollouts({ sessionIds }) {
|
|
@@ -23,7 +23,7 @@ import { deliverPeerReply, type RemoteReplyRoute } from "#mcp/session-comm/peer-
|
|
|
23
23
|
import { type CoalescingRunner, createCoalescingRunner } from "#outbox/coalescing-runner";
|
|
24
24
|
import { deleteProcessingFile, drainOutboxFile, parseOutboxLine } from "#outbox/file-ops";
|
|
25
25
|
import { watchDir } from "#outbox/utils";
|
|
26
|
-
import {
|
|
26
|
+
import { SESSION_INBOX_DIR } from "#platform/config";
|
|
27
27
|
import { FROM_AUTO_CONTINUE, FROM_SELF_SCHEDULE } from "#platform/constants";
|
|
28
28
|
import { appendJsonlEntry, readJsonlLines } from "#platform/jsonl";
|
|
29
29
|
import { logger } from "#platform/logger";
|
|
@@ -123,16 +123,22 @@ export function normalizeAskTarget(target: unknown): "main" | "cron" | null {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
/**
|
|
126
|
-
*
|
|
126
|
+
* Every ask now runs as a normal turn on the target's own session
|
|
127
127
|
* (tell_session-style, visible in the target) instead of on a fork. Codex
|
|
128
128
|
* scopes its prompt cache to the thread, so a fork's first request never
|
|
129
129
|
* reused the parent's cache and every ask paid full input price; running on
|
|
130
|
-
* the target session keeps the cache warm
|
|
131
|
-
*
|
|
132
|
-
*
|
|
130
|
+
* the target session keeps the cache warm.
|
|
131
|
+
*
|
|
132
|
+
* Cron asks (2026-10-20) dropped the fork too, trading it for
|
|
133
|
+
* `turnConcurrency: "isolated"` so the ask doesn't queue behind the topic's
|
|
134
|
+
* main visible turn. This does NOT serialize against a scheduled cron run
|
|
135
|
+
* landing on the identical session at the same moment — the fork used to
|
|
136
|
+
* sidestep that by mutating a private copy instead of the shared session.
|
|
137
|
+
* Treat a corrupted/interleaved cron rollout as a known residual risk until
|
|
138
|
+
* the cron scheduler itself gets a per-session lock.
|
|
133
139
|
*/
|
|
134
|
-
export function askRunsOnTargetSession(
|
|
135
|
-
return
|
|
140
|
+
export function askRunsOnTargetSession(_target: "main" | "cron"): boolean {
|
|
141
|
+
return true;
|
|
136
142
|
}
|
|
137
143
|
|
|
138
144
|
export function resolveAskParentSessionId(
|
|
@@ -1070,33 +1076,16 @@ async function handleAskEntry(
|
|
|
1070
1076
|
const { getTopic } = await import("#storage/api-topics");
|
|
1071
1077
|
const fullTopic = getTopic(topic.id);
|
|
1072
1078
|
const agentOverride = (fullTopic?.agent ?? topic.agent ?? undefined) as AgentKind | undefined;
|
|
1073
|
-
const cwd = resolveTopicWorkspaceDir(topic.id);
|
|
1074
1079
|
if (!agentOverride) {
|
|
1075
1080
|
clearPendingAskForEntry(scope, entry, topicName, "target-agent-missing");
|
|
1076
1081
|
await notifyAskDrop(scope, entry, topicName, "(error: target topic has no agent)");
|
|
1077
1082
|
return;
|
|
1078
1083
|
}
|
|
1079
1084
|
|
|
1080
|
-
//
|
|
1081
|
-
//
|
|
1082
|
-
//
|
|
1083
|
-
|
|
1084
|
-
const { getRegistry, getRegistryOperations } = await import("#agents/registry");
|
|
1085
|
-
const { resolveModelForAgent } = await import("#agents/model-catalog");
|
|
1086
|
-
const { getApiTopicConfig } = await import("#storage/api-topic-config");
|
|
1087
|
-
const { readConversation } = await import("#storage/conversations");
|
|
1088
|
-
const registry = getRegistry(agentOverride);
|
|
1089
|
-
const topicConfig = getApiTopicConfig(topic.id);
|
|
1090
|
-
const model = resolveModelForAgent(
|
|
1091
|
-
agentOverride,
|
|
1092
|
-
topicConfig?.model ?? fullTopic?.defaultModel,
|
|
1093
|
-
registry,
|
|
1094
|
-
);
|
|
1095
|
-
const requestedEffort = topicConfig?.effort ?? fullTopic?.defaultEffort;
|
|
1096
|
-
const effort =
|
|
1097
|
-
requestedEffort && registry.validateEffort(requestedEffort)
|
|
1098
|
-
? requestedEffort
|
|
1099
|
-
: registry.defaultEffort;
|
|
1085
|
+
// Every ask runs as a normal turn on the target's own session (see
|
|
1086
|
+
// askRunsOnTargetSession above), so no fork/model/effort resolution is
|
|
1087
|
+
// needed here — triggerTopicAiTurn below resolves those itself, same as
|
|
1088
|
+
// a regular tell_session.
|
|
1100
1089
|
const cronSession =
|
|
1101
1090
|
askTarget === "cron" ? getRegisteredCronSession(topic.id, agentOverride) : null;
|
|
1102
1091
|
const parentSessionId =
|
|
@@ -1161,6 +1150,17 @@ async function handleAskEntry(
|
|
|
1161
1150
|
label: `Ask from ${fromLabel}`,
|
|
1162
1151
|
message: entry.message,
|
|
1163
1152
|
},
|
|
1153
|
+
// cron: target the cron session explicitly and run it isolated from
|
|
1154
|
+
// the topic's main visible turn (no fork — see askRunsOnTargetSession's
|
|
1155
|
+
// doc comment for the residual concurrency tradeoff). main: default to
|
|
1156
|
+
// the topic's own session/turn slot, same as tell_session.
|
|
1157
|
+
...(askTarget === "cron"
|
|
1158
|
+
? {
|
|
1159
|
+
sessionId: parentSessionId,
|
|
1160
|
+
sessionScope: "isolated" as const,
|
|
1161
|
+
turnConcurrency: "isolated" as const,
|
|
1162
|
+
}
|
|
1163
|
+
: {}),
|
|
1164
1164
|
onSettled,
|
|
1165
1165
|
onDispatched,
|
|
1166
1166
|
});
|
|
@@ -1171,149 +1171,11 @@ async function handleAskEntry(
|
|
|
1171
1171
|
return;
|
|
1172
1172
|
}
|
|
1173
1173
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
cwd,
|
|
1180
|
-
userId: scope.userId,
|
|
1181
|
-
topicName,
|
|
1182
|
-
title: `ask: ${entry.from} -> ${targetLabel}`,
|
|
1183
|
-
model,
|
|
1184
|
-
...(effort ? { effort } : {}),
|
|
1185
|
-
})
|
|
1186
|
-
: undefined;
|
|
1187
|
-
|
|
1188
|
-
let forkPlan: AskForkPlan;
|
|
1189
|
-
try {
|
|
1190
|
-
forkPlan = await createAskForkPlan({
|
|
1191
|
-
// Read once: queued/replayed asks must keep the history visible at inbox
|
|
1192
|
-
// consumption time even if the target topic advances while they wait.
|
|
1193
|
-
entries: askTarget === "cron" ? [] : readConversation(scope.userId, topicName),
|
|
1194
|
-
...(forkNative ? { forkNative } : {}),
|
|
1195
|
-
...(askTarget === "cron" && forkNative ? { prepareSession: forkNative } : {}),
|
|
1196
|
-
synthesize: async (entries) => {
|
|
1197
|
-
if (askTarget === "cron") {
|
|
1198
|
-
throw new Error("cron session fork failed");
|
|
1199
|
-
}
|
|
1200
|
-
const rollout = getRegistryOperations(agentOverride).writeRollout({
|
|
1201
|
-
cwd,
|
|
1202
|
-
entries,
|
|
1203
|
-
model,
|
|
1204
|
-
...(effort ? { effort } : {}),
|
|
1205
|
-
});
|
|
1206
|
-
return {
|
|
1207
|
-
agent: agentOverride,
|
|
1208
|
-
forkId: rollout.sessionId,
|
|
1209
|
-
rolloutPath: rollout.rolloutPath,
|
|
1210
|
-
};
|
|
1211
|
-
},
|
|
1212
|
-
onNativeForkError: (err) => {
|
|
1213
|
-
logger.warn(
|
|
1214
|
-
{ err, topicName, from: entry.from, requestId, parentSessionId },
|
|
1215
|
-
"session-inbox: native target fork failed",
|
|
1216
|
-
);
|
|
1217
|
-
},
|
|
1218
|
-
});
|
|
1219
|
-
} catch (err) {
|
|
1220
|
-
// This entry has already been claimed. Convert preparation failure into a
|
|
1221
|
-
// terminal ask result so processTopicInbox can delete its `.processing`
|
|
1222
|
-
// file instead of leaving the request undiscoverable until another write.
|
|
1223
|
-
logger.error(
|
|
1224
|
-
{ err, topicName, from: entry.from, requestId, parentSessionId },
|
|
1225
|
-
"session-inbox: failed to prepare target ask session",
|
|
1226
|
-
);
|
|
1227
|
-
try {
|
|
1228
|
-
clearPendingAskForEntry(scope, entry, topicName, "target-session-prepare-failed");
|
|
1229
|
-
} catch (clearError) {
|
|
1230
|
-
logger.warn(
|
|
1231
|
-
{ err: clearError, topicName, from: entry.from, requestId },
|
|
1232
|
-
"session-inbox: failed to clear ask after session preparation failure",
|
|
1233
|
-
);
|
|
1234
|
-
}
|
|
1235
|
-
const message = "(error: target session could not be prepared)";
|
|
1236
|
-
if (remoteReply) {
|
|
1237
|
-
try {
|
|
1238
|
-
const delivered = await deliverPeerReply(remoteReply, targetLabel, message, "error");
|
|
1239
|
-
if (!delivered) {
|
|
1240
|
-
logger.warn(
|
|
1241
|
-
{ topicName, from: entry.from, requestId },
|
|
1242
|
-
"session-inbox: failed to deliver remote ask preparation error",
|
|
1243
|
-
);
|
|
1244
|
-
}
|
|
1245
|
-
} catch (deliveryError) {
|
|
1246
|
-
logger.warn(
|
|
1247
|
-
{ err: deliveryError, topicName, from: entry.from, requestId },
|
|
1248
|
-
"session-inbox: remote ask preparation error delivery threw",
|
|
1249
|
-
);
|
|
1250
|
-
}
|
|
1251
|
-
} else {
|
|
1252
|
-
try {
|
|
1253
|
-
await notifyAskDrop(scope, entry, targetLabel, message);
|
|
1254
|
-
} catch (deliveryError) {
|
|
1255
|
-
logger.warn(
|
|
1256
|
-
{ err: deliveryError, topicName, from: entry.from, requestId },
|
|
1257
|
-
"session-inbox: local ask preparation error delivery threw",
|
|
1258
|
-
);
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
return;
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
if (askTarget === "cron") {
|
|
1265
|
-
const currentCronSession = getRegisteredCronSession(topic.id, agentOverride);
|
|
1266
|
-
if (
|
|
1267
|
-
!cronSession ||
|
|
1268
|
-
!currentCronSession ||
|
|
1269
|
-
currentCronSession.sessionId !== cronSession.sessionId ||
|
|
1270
|
-
currentCronSession.ownerUserId !== cronSession.ownerUserId
|
|
1271
|
-
) {
|
|
1272
|
-
cleanupAgentFork(forkPlan.forkHandle);
|
|
1273
|
-
clearPendingAskForEntry(scope, entry, topicName, "cron-session-changed-during-fork");
|
|
1274
|
-
const message =
|
|
1275
|
-
"(error: cron session changed while preparing the request; retry the request)";
|
|
1276
|
-
if (remoteReply) {
|
|
1277
|
-
await deliverPeerReply(remoteReply, targetLabel, message, "error");
|
|
1278
|
-
} else {
|
|
1279
|
-
await notifyAskDrop(scope, entry, targetLabel, message);
|
|
1280
|
-
}
|
|
1281
|
-
return;
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
// Trigger the AI turn immediately with the pre-forked session.
|
|
1286
|
-
triggerTopicAiTurn(
|
|
1287
|
-
topic.id,
|
|
1288
|
-
String(scope.userId),
|
|
1289
|
-
prompt,
|
|
1290
|
-
(agentOverride ?? topic.agent ?? undefined) as Parameters<typeof triggerTopicAiTurn>[3],
|
|
1291
|
-
{
|
|
1292
|
-
origin: entry.from,
|
|
1293
|
-
requestId,
|
|
1294
|
-
contextId: entry.contextId,
|
|
1295
|
-
depth: (entry.fromDepth ?? 0) + 1,
|
|
1296
|
-
silent: true,
|
|
1297
|
-
sessionId: forkPlan.forkHandle.forkId,
|
|
1298
|
-
// The fork is read-only and session-isolated, so it does not need the
|
|
1299
|
-
// target topic's visible execution slot. Answer immediately even while
|
|
1300
|
-
// that topic is processing a user turn.
|
|
1301
|
-
turnConcurrency: "isolated",
|
|
1302
|
-
forkHandle: forkPlan.forkHandle,
|
|
1303
|
-
// Keep a regeneration recipe even though the first fork is eager. The
|
|
1304
|
-
// isolated room prevents user preemption; the recipe remains available
|
|
1305
|
-
// for a clean retry if the provider reports an expired session.
|
|
1306
|
-
prepareSession: forkPlan.prepareSession,
|
|
1307
|
-
cwd,
|
|
1308
|
-
onSettled,
|
|
1309
|
-
onDispatched,
|
|
1310
|
-
},
|
|
1311
|
-
);
|
|
1312
|
-
|
|
1313
|
-
logger.info(
|
|
1314
|
-
{ topicName, from: entry.from, requestId, callerTopicId },
|
|
1315
|
-
"session-inbox: ask delivered",
|
|
1316
|
-
);
|
|
1174
|
+
// Every ask now runs on the target's own session (see
|
|
1175
|
+
// askRunsOnTargetSession above) — the fork-and-copy path this used to
|
|
1176
|
+
// fall through to for ask_cron was removed 2026-10-20. createAskForkPlan
|
|
1177
|
+
// itself is kept (still exported + unit-tested) in case a future caller
|
|
1178
|
+
// needs a true read-only copy again.
|
|
1317
1179
|
}
|
|
1318
1180
|
|
|
1319
1181
|
// ── Bootstrap ───────────────────────────────────────────────────────
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const NEGOTIUM_VERSION = "0.22.
|
|
1
|
+
export const NEGOTIUM_VERSION = "0.22.3";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const NEGOTIUM_VERSION = "0.22.
|
|
1
|
+
export declare const NEGOTIUM_VERSION = "0.22.3";
|