remote-codex 0.11.40 → 0.11.42
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/apps/relay-server/dist/index.js +136 -49
- package/apps/supervisor-api/dist/index.js +166 -20
- package/apps/supervisor-web/dist/assets/index-BcCLYWAf.css +1 -0
- package/apps/supervisor-web/dist/assets/index-BvxWpPKW.js +21 -0
- package/apps/supervisor-web/dist/assets/{thread-ui-xWvswa2v.js → thread-ui-BLThton-.js} +40 -38
- package/apps/supervisor-web/dist/index.html +3 -3
- package/package.json +1 -1
- package/packages/claude/src/runtimeAdapter.test.ts +135 -0
- package/packages/claude/src/runtimeAdapter.ts +156 -5
- package/packages/shared/src/index.ts +91 -1
- package/apps/supervisor-web/dist/assets/index-B-qj7e8G.js +0 -21
- package/apps/supervisor-web/dist/assets/index-CDFuOeJN.css +0 -1
|
@@ -675,6 +675,20 @@ var RelayStore = class _RelayStore {
|
|
|
675
675
|
).get(deviceId, userId);
|
|
676
676
|
return row ? { sandboxId: row.id, enabled: Boolean(row.workspace_isolation_enabled) } : null;
|
|
677
677
|
}
|
|
678
|
+
hostedWorkspaceBootstrapContext(deviceId) {
|
|
679
|
+
const sandbox = this.hostedWorkspaceIsolation(deviceId);
|
|
680
|
+
if (!sandbox?.enabled) {
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
const users = this.sqlite.prepare(
|
|
684
|
+
`SELECT u.*
|
|
685
|
+
FROM relay_hosted_sandbox_members m
|
|
686
|
+
JOIN relay_users u ON u.id = m.user_id
|
|
687
|
+
WHERE m.sandbox_id = ? AND u.enabled = 1
|
|
688
|
+
ORDER BY m.position ASC, m.created_at ASC`
|
|
689
|
+
).all(sandbox.sandboxId).map((row) => this.rowToUser(row)).filter((user) => Boolean(user)).map((user) => this.publicUser(user));
|
|
690
|
+
return { sandboxId: sandbox.sandboxId, users };
|
|
691
|
+
}
|
|
678
692
|
hostedUserWorkspaceIds(sandboxId, userId) {
|
|
679
693
|
return this.sqlite.prepare(
|
|
680
694
|
`SELECT workspace_id FROM relay_hosted_user_workspaces
|
|
@@ -683,11 +697,24 @@ var RelayStore = class _RelayStore {
|
|
|
683
697
|
}
|
|
684
698
|
recordHostedUserWorkspace(sandboxId, userId, workspaceId, initial = false) {
|
|
685
699
|
this.sqlite.prepare(
|
|
686
|
-
`INSERT
|
|
700
|
+
`INSERT INTO relay_hosted_user_workspaces
|
|
687
701
|
(sandbox_id, user_id, workspace_id, initial_workspace, created_at)
|
|
688
|
-
VALUES (?, ?, ?, ?, ?)
|
|
702
|
+
VALUES (?, ?, ?, ?, ?)
|
|
703
|
+
ON CONFLICT(sandbox_id, workspace_id) DO UPDATE SET
|
|
704
|
+
initial_workspace = MAX(
|
|
705
|
+
relay_hosted_user_workspaces.initial_workspace,
|
|
706
|
+
excluded.initial_workspace
|
|
707
|
+
)`
|
|
689
708
|
).run(sandboxId, userId, workspaceId, initial ? 1 : 0, (/* @__PURE__ */ new Date()).toISOString());
|
|
690
709
|
}
|
|
710
|
+
hostedInitialWorkspaceId(sandboxId, userId) {
|
|
711
|
+
const row = this.sqlite.prepare(
|
|
712
|
+
`SELECT workspace_id FROM relay_hosted_user_workspaces
|
|
713
|
+
WHERE sandbox_id = ? AND user_id = ? AND initial_workspace = 1
|
|
714
|
+
ORDER BY created_at ASC LIMIT 1`
|
|
715
|
+
).get(sandboxId, userId);
|
|
716
|
+
return row?.workspace_id ?? null;
|
|
717
|
+
}
|
|
691
718
|
ownsHostedWorkspace(sandboxId, userId, workspaceId) {
|
|
692
719
|
return Boolean(
|
|
693
720
|
this.sqlite.prepare(
|
|
@@ -711,6 +738,15 @@ var RelayStore = class _RelayStore {
|
|
|
711
738
|
).get(sandboxId, userId, threadId)
|
|
712
739
|
);
|
|
713
740
|
}
|
|
741
|
+
hasHostedUserThreadInWorkspace(sandboxId, userId, workspaceId) {
|
|
742
|
+
return Boolean(
|
|
743
|
+
this.sqlite.prepare(
|
|
744
|
+
`SELECT 1 FROM relay_hosted_user_threads
|
|
745
|
+
WHERE sandbox_id = ? AND user_id = ? AND workspace_id = ?
|
|
746
|
+
LIMIT 1`
|
|
747
|
+
).get(sandboxId, userId, workspaceId)
|
|
748
|
+
);
|
|
749
|
+
}
|
|
714
750
|
listHostedProviderRecords() {
|
|
715
751
|
return this.sqlite.prepare(
|
|
716
752
|
"SELECT id, credential_ref FROM relay_hosted_sandboxes ORDER BY id"
|
|
@@ -3951,6 +3987,37 @@ function buildRelayServer(config2, options = {}) {
|
|
|
3951
3987
|
hostedSandboxProvider,
|
|
3952
3988
|
config2.hostedSandbox
|
|
3953
3989
|
);
|
|
3990
|
+
const scheduleHostedUserBootstraps = (deviceId) => {
|
|
3991
|
+
const context = store.hostedWorkspaceBootstrapContext(deviceId);
|
|
3992
|
+
const supervisor = state.supervisors.get(deviceId);
|
|
3993
|
+
if (!context || !supervisor || supervisor.socket.readyState !== WEBSOCKET_OPEN) {
|
|
3994
|
+
return;
|
|
3995
|
+
}
|
|
3996
|
+
void Promise.allSettled(
|
|
3997
|
+
context.users.map(
|
|
3998
|
+
(bootstrapUser) => ensureHostedUserBootstrap({
|
|
3999
|
+
store,
|
|
4000
|
+
supervisor,
|
|
4001
|
+
deviceId,
|
|
4002
|
+
sandboxId: context.sandboxId,
|
|
4003
|
+
user: bootstrapUser
|
|
4004
|
+
})
|
|
4005
|
+
)
|
|
4006
|
+
).then((results) => {
|
|
4007
|
+
results.forEach((result, index) => {
|
|
4008
|
+
if (result.status === "rejected") {
|
|
4009
|
+
app2.log.warn(
|
|
4010
|
+
{
|
|
4011
|
+
err: result.reason,
|
|
4012
|
+
deviceId,
|
|
4013
|
+
userId: context.users[index]?.id
|
|
4014
|
+
},
|
|
4015
|
+
"Hosted VM user bootstrap failed."
|
|
4016
|
+
);
|
|
4017
|
+
}
|
|
4018
|
+
});
|
|
4019
|
+
});
|
|
4020
|
+
};
|
|
3954
4021
|
const allowedWebViewCorsOrigins = webViewCorsOrigins(
|
|
3955
4022
|
options.env ?? process.env
|
|
3956
4023
|
);
|
|
@@ -4303,10 +4370,12 @@ function buildRelayServer(config2, options = {}) {
|
|
|
4303
4370
|
}
|
|
4304
4371
|
const { sandboxId } = z.object({ sandboxId: z.string().uuid() }).parse(request.params);
|
|
4305
4372
|
const body = updateHostedSandboxMembersSchema.parse(request.body ?? {});
|
|
4306
|
-
|
|
4373
|
+
const sandbox = hostedSandboxService.updateMembers(
|
|
4307
4374
|
sandboxId,
|
|
4308
4375
|
body.assignedUserIds
|
|
4309
4376
|
);
|
|
4377
|
+
scheduleHostedUserBootstraps(sandbox.deviceId);
|
|
4378
|
+
return sandbox;
|
|
4310
4379
|
}
|
|
4311
4380
|
);
|
|
4312
4381
|
app2.patch(
|
|
@@ -4316,10 +4385,14 @@ function buildRelayServer(config2, options = {}) {
|
|
|
4316
4385
|
if (!user) return;
|
|
4317
4386
|
const { sandboxId } = z.object({ sandboxId: z.string().uuid() }).parse(request.params);
|
|
4318
4387
|
const body = updateHostedSandboxSettingsSchema.parse(request.body ?? {});
|
|
4319
|
-
|
|
4388
|
+
const sandbox = store.setHostedWorkspaceIsolation(
|
|
4320
4389
|
sandboxId,
|
|
4321
4390
|
body.workspaceIsolationEnabled
|
|
4322
4391
|
);
|
|
4392
|
+
if (sandbox.workspaceIsolationEnabled) {
|
|
4393
|
+
scheduleHostedUserBootstraps(sandbox.deviceId);
|
|
4394
|
+
}
|
|
4395
|
+
return sandbox;
|
|
4323
4396
|
}
|
|
4324
4397
|
);
|
|
4325
4398
|
app2.post(
|
|
@@ -4681,6 +4754,7 @@ function buildRelayServer(config2, options = {}) {
|
|
|
4681
4754
|
clientConnection.socket.close();
|
|
4682
4755
|
}
|
|
4683
4756
|
});
|
|
4757
|
+
scheduleHostedUserBootstraps(deviceId);
|
|
4684
4758
|
}
|
|
4685
4759
|
});
|
|
4686
4760
|
realtimeApp.route({
|
|
@@ -5073,9 +5147,6 @@ async function forwardSharedThreadList(input) {
|
|
|
5073
5147
|
);
|
|
5074
5148
|
}
|
|
5075
5149
|
async function ensureHostedUserBootstrap(input) {
|
|
5076
|
-
if (input.store.hostedUserWorkspaceIds(input.sandboxId, input.user.id).length) {
|
|
5077
|
-
return;
|
|
5078
|
-
}
|
|
5079
5150
|
const key = `${input.sandboxId}:${input.user.id}`;
|
|
5080
5151
|
const existing = hostedBootstrapPromises.get(key);
|
|
5081
5152
|
if (existing) return existing;
|
|
@@ -5084,55 +5155,69 @@ async function ensureHostedUserBootstrap(input) {
|
|
|
5084
5155
|
const directory = `${slug}-${input.user.id.slice(0, 8)}`;
|
|
5085
5156
|
const absoluteDirectory = `/home/remote-codex/workspaces/${directory}`;
|
|
5086
5157
|
const label = `${input.user.username}'s workspace`;
|
|
5087
|
-
|
|
5088
|
-
input.
|
|
5089
|
-
input.
|
|
5090
|
-
"GET",
|
|
5091
|
-
"/api/workspaces"
|
|
5092
|
-
);
|
|
5093
|
-
const currentWorkspaces = Array.isArray(current) ? current : [];
|
|
5094
|
-
let workspace = currentWorkspaces.find(
|
|
5095
|
-
(candidate) => isObject(candidate) && typeof candidate.absPath === "string" && candidate.absPath === absoluteDirectory
|
|
5158
|
+
let workspaceId = input.store.hostedInitialWorkspaceId(
|
|
5159
|
+
input.sandboxId,
|
|
5160
|
+
input.user.id
|
|
5096
5161
|
);
|
|
5097
|
-
if (!
|
|
5098
|
-
|
|
5162
|
+
if (!workspaceId) {
|
|
5163
|
+
const current = await forwardSupervisorCommandJson(
|
|
5099
5164
|
input.supervisor,
|
|
5100
5165
|
input.deviceId,
|
|
5101
|
-
"
|
|
5102
|
-
"/api/workspaces"
|
|
5103
|
-
{ absPath: absoluteDirectory, label }
|
|
5166
|
+
"GET",
|
|
5167
|
+
"/api/workspaces"
|
|
5104
5168
|
);
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
model: "gpt-5.6-sol",
|
|
5118
|
-
reasoningEffort: "low",
|
|
5119
|
-
approvalMode: "yolo"
|
|
5169
|
+
const currentWorkspaces = Array.isArray(current) ? current : [];
|
|
5170
|
+
let workspace = currentWorkspaces.find(
|
|
5171
|
+
(candidate) => isObject(candidate) && typeof candidate.absPath === "string" && candidate.absPath === absoluteDirectory
|
|
5172
|
+
);
|
|
5173
|
+
if (!workspace) {
|
|
5174
|
+
workspace = await forwardSupervisorCommandJson(
|
|
5175
|
+
input.supervisor,
|
|
5176
|
+
input.deviceId,
|
|
5177
|
+
"POST",
|
|
5178
|
+
"/api/workspaces",
|
|
5179
|
+
{ absPath: absoluteDirectory, label }
|
|
5180
|
+
);
|
|
5120
5181
|
}
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
input.
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5182
|
+
workspaceId = stringField(workspace, "id");
|
|
5183
|
+
if (!workspaceId) {
|
|
5184
|
+
throw new Error("Initial workspace creation returned no id.");
|
|
5185
|
+
}
|
|
5186
|
+
input.store.recordHostedUserWorkspace(
|
|
5187
|
+
input.sandboxId,
|
|
5188
|
+
input.user.id,
|
|
5189
|
+
workspaceId,
|
|
5190
|
+
true
|
|
5191
|
+
);
|
|
5192
|
+
}
|
|
5193
|
+
if (!input.store.hasHostedUserThreadInWorkspace(
|
|
5131
5194
|
input.sandboxId,
|
|
5132
5195
|
input.user.id,
|
|
5133
|
-
threadId,
|
|
5134
5196
|
workspaceId
|
|
5135
|
-
)
|
|
5197
|
+
)) {
|
|
5198
|
+
const thread = await forwardSupervisorCommandJson(
|
|
5199
|
+
input.supervisor,
|
|
5200
|
+
input.deviceId,
|
|
5201
|
+
"POST",
|
|
5202
|
+
"/api/threads/start",
|
|
5203
|
+
{
|
|
5204
|
+
workspaceId,
|
|
5205
|
+
title: "Getting started",
|
|
5206
|
+
provider: "codex",
|
|
5207
|
+
model: "gpt-5.6-sol",
|
|
5208
|
+
reasoningEffort: "low",
|
|
5209
|
+
approvalMode: "yolo"
|
|
5210
|
+
}
|
|
5211
|
+
);
|
|
5212
|
+
const threadId = stringField(thread, "id");
|
|
5213
|
+
if (!threadId) throw new Error("Initial thread creation returned no id.");
|
|
5214
|
+
input.store.recordHostedUserThread(
|
|
5215
|
+
input.sandboxId,
|
|
5216
|
+
input.user.id,
|
|
5217
|
+
threadId,
|
|
5218
|
+
workspaceId
|
|
5219
|
+
);
|
|
5220
|
+
}
|
|
5136
5221
|
})().finally(() => hostedBootstrapPromises.delete(key));
|
|
5137
5222
|
hostedBootstrapPromises.set(key, pending);
|
|
5138
5223
|
return pending;
|
|
@@ -5763,7 +5848,9 @@ function isAllowedSharedRuntimeMetadataRequest(method, pathname) {
|
|
|
5763
5848
|
function threadIdFromPath(pathValue) {
|
|
5764
5849
|
const pathname = new URL(pathValue, "http://relay.local").pathname;
|
|
5765
5850
|
const match = /^\/api\/threads\/([^/?#]+)/.exec(pathname);
|
|
5766
|
-
|
|
5851
|
+
if (!match) return null;
|
|
5852
|
+
const threadId = decodeURIComponent(match[1]);
|
|
5853
|
+
return threadId === "start" || threadId === "import" ? null : threadId;
|
|
5767
5854
|
}
|
|
5768
5855
|
function workspaceIdFromPath(pathValue) {
|
|
5769
5856
|
const pathname = new URL(pathValue, "http://relay.local").pathname;
|
|
@@ -11717,7 +11717,40 @@ import { pathToFileURL } from "url";
|
|
|
11717
11717
|
import { promisify } from "util";
|
|
11718
11718
|
var execFileAsync = promisify(execFile);
|
|
11719
11719
|
var promptPhotoTokenPattern2 = /\[PHOTO\s+([^\]]+)\]/g;
|
|
11720
|
+
var claudeCompactSummaryPrefix = "This session is being continued from a previous conversation that ran out of context.";
|
|
11720
11721
|
var activeTranscriptMatchWindowMs = 12e4;
|
|
11722
|
+
function isClaudeCompactBoundaryMessage(message) {
|
|
11723
|
+
if (message.type !== "system") {
|
|
11724
|
+
return false;
|
|
11725
|
+
}
|
|
11726
|
+
if (message.subtype === "compact_boundary" || message.compact_metadata) {
|
|
11727
|
+
return true;
|
|
11728
|
+
}
|
|
11729
|
+
return isRecord8(message.message) && (message.message.subtype === "compact_boundary" || isRecord8(message.message.compact_metadata));
|
|
11730
|
+
}
|
|
11731
|
+
function isClaudeCompactSummaryMessage(message) {
|
|
11732
|
+
if (message.type !== "user" || message.parent_tool_use_id) {
|
|
11733
|
+
return false;
|
|
11734
|
+
}
|
|
11735
|
+
const payload = isRecord8(message.message) ? message.message : null;
|
|
11736
|
+
if (message.isCompactSummary || message.is_compact_summary || payload?.isCompactSummary === true || payload?.is_compact_summary === true) {
|
|
11737
|
+
return true;
|
|
11738
|
+
}
|
|
11739
|
+
return messageContentText(message.message).trim().startsWith(claudeCompactSummaryPrefix);
|
|
11740
|
+
}
|
|
11741
|
+
function claudeContextCompactionItem(id, status, error = null) {
|
|
11742
|
+
const completed = status === "completed";
|
|
11743
|
+
const failed = status === "failed";
|
|
11744
|
+
const text2 = failed ? "Context compaction failed" : completed ? "Context compacted" : "Compacting context";
|
|
11745
|
+
return {
|
|
11746
|
+
id,
|
|
11747
|
+
kind: "contextCompaction",
|
|
11748
|
+
text: text2,
|
|
11749
|
+
previewText: text2,
|
|
11750
|
+
detailText: failed ? error : null,
|
|
11751
|
+
status
|
|
11752
|
+
};
|
|
11753
|
+
}
|
|
11721
11754
|
function normalizePromptForTurnReconciliation(value) {
|
|
11722
11755
|
return value.replace(/\[PHOTO\s+[^\]]+\]/g, " ").replace(/\s+/g, " ").trim();
|
|
11723
11756
|
}
|
|
@@ -12923,7 +12956,8 @@ var ClaudeRuntimeAdapter = class extends EventEmitter5 {
|
|
|
12923
12956
|
suppressedToolUseIds: /* @__PURE__ */ new Set(),
|
|
12924
12957
|
assistantUsage: null,
|
|
12925
12958
|
resultUsage: null,
|
|
12926
|
-
modelContextWindow: null
|
|
12959
|
+
modelContextWindow: null,
|
|
12960
|
+
currentCompactionItemId: null
|
|
12927
12961
|
};
|
|
12928
12962
|
this.knownSessionIds.add(input.providerSessionId);
|
|
12929
12963
|
let sessionPrompts = this.liveUserPrompts.get(input.providerSessionId);
|
|
@@ -13147,6 +13181,53 @@ var ClaudeRuntimeAdapter = class extends EventEmitter5 {
|
|
|
13147
13181
|
}
|
|
13148
13182
|
return;
|
|
13149
13183
|
}
|
|
13184
|
+
if (message.type === "system" && message.subtype === "status") {
|
|
13185
|
+
if (message.status === "compacting") {
|
|
13186
|
+
const existing = state.currentCompactionItemId ? state.items.get(state.currentCompactionItemId) : null;
|
|
13187
|
+
const itemId = existing?.status === "running" ? existing.id : `claude-compaction-${messageUuid(message, randomUUID2())}`;
|
|
13188
|
+
state.currentCompactionItemId = itemId;
|
|
13189
|
+
const item = withHistoryItemCreatedAt(
|
|
13190
|
+
claudeContextCompactionItem(itemId, "running"),
|
|
13191
|
+
messageCreatedAt
|
|
13192
|
+
);
|
|
13193
|
+
addOrUpdateItem(state, item);
|
|
13194
|
+
this.emitItem(state, item, "item.started", { force: Boolean(existing) });
|
|
13195
|
+
return;
|
|
13196
|
+
}
|
|
13197
|
+
if (message.compact_result) {
|
|
13198
|
+
const itemId = state.currentCompactionItemId ?? `claude-compaction-${messageUuid(message, randomUUID2())}`;
|
|
13199
|
+
const status = message.compact_result === "success" ? "completed" : "failed";
|
|
13200
|
+
const item = withHistoryItemCreatedAt(
|
|
13201
|
+
claudeContextCompactionItem(itemId, status, message.compact_error ?? null),
|
|
13202
|
+
messageCreatedAt
|
|
13203
|
+
);
|
|
13204
|
+
addOrUpdateItem(state, item);
|
|
13205
|
+
this.emitItem(state, item, "item.completed");
|
|
13206
|
+
state.currentCompactionItemId = status === "failed" ? null : itemId;
|
|
13207
|
+
return;
|
|
13208
|
+
}
|
|
13209
|
+
return;
|
|
13210
|
+
}
|
|
13211
|
+
if (isClaudeCompactBoundaryMessage(message)) {
|
|
13212
|
+
const itemId = state.currentCompactionItemId ?? `claude-compaction-${messageUuid(message, randomUUID2())}`;
|
|
13213
|
+
const previous = state.items.get(itemId);
|
|
13214
|
+
const item = withHistoryItemCreatedAt(
|
|
13215
|
+
claudeContextCompactionItem(itemId, "completed"),
|
|
13216
|
+
previous?.createdAt ?? messageCreatedAt
|
|
13217
|
+
);
|
|
13218
|
+
addOrUpdateItem(state, item);
|
|
13219
|
+
if (!previous) {
|
|
13220
|
+
this.emitItem(state, item, "item.started");
|
|
13221
|
+
}
|
|
13222
|
+
if (previous?.status !== "completed") {
|
|
13223
|
+
this.emitItem(state, item, "item.completed");
|
|
13224
|
+
}
|
|
13225
|
+
state.currentCompactionItemId = null;
|
|
13226
|
+
return;
|
|
13227
|
+
}
|
|
13228
|
+
if (isClaudeCompactSummaryMessage(message)) {
|
|
13229
|
+
return;
|
|
13230
|
+
}
|
|
13150
13231
|
if (message.type === "stream_event") {
|
|
13151
13232
|
const nextStreamMessageId = streamMessageId(message.event);
|
|
13152
13233
|
if (nextStreamMessageId) {
|
|
@@ -13498,6 +13579,8 @@ var ClaudeRuntimeAdapter = class extends EventEmitter5 {
|
|
|
13498
13579
|
const turns = [];
|
|
13499
13580
|
let current = null;
|
|
13500
13581
|
let skippingHiddenInit = false;
|
|
13582
|
+
let pendingCompactSummaryItemId = null;
|
|
13583
|
+
let pendingCompactBoundaryItemId = null;
|
|
13501
13584
|
const suppressedToolUseIds = /* @__PURE__ */ new Set();
|
|
13502
13585
|
const upsertCurrentItem = (item) => {
|
|
13503
13586
|
if (!current) {
|
|
@@ -13529,6 +13612,18 @@ var ClaudeRuntimeAdapter = class extends EventEmitter5 {
|
|
|
13529
13612
|
current.itemsById.set(item.id, item);
|
|
13530
13613
|
};
|
|
13531
13614
|
for (const message of messages) {
|
|
13615
|
+
if (isClaudeCompactBoundaryMessage(message)) {
|
|
13616
|
+
const itemId = pendingCompactSummaryItemId ?? `claude-compaction-${messageUuid(message, randomUUID2())}`;
|
|
13617
|
+
pendingCompactSummaryItemId = null;
|
|
13618
|
+
pendingCompactBoundaryItemId = itemId;
|
|
13619
|
+
upsertCurrentItem(
|
|
13620
|
+
withHistoryItemCreatedAt(
|
|
13621
|
+
claudeContextCompactionItem(itemId, "completed"),
|
|
13622
|
+
sessionMessageTimestamp(message) ?? current?.startedAt
|
|
13623
|
+
)
|
|
13624
|
+
);
|
|
13625
|
+
continue;
|
|
13626
|
+
}
|
|
13532
13627
|
if (message.type === "user") {
|
|
13533
13628
|
const taskNotification = taskNotificationToolResult(message.message);
|
|
13534
13629
|
if (taskNotification) {
|
|
@@ -13573,6 +13668,20 @@ var ClaudeRuntimeAdapter = class extends EventEmitter5 {
|
|
|
13573
13668
|
}
|
|
13574
13669
|
}
|
|
13575
13670
|
if (message.type === "user" && !message.parent_tool_use_id) {
|
|
13671
|
+
if (isClaudeCompactSummaryMessage(message)) {
|
|
13672
|
+
const itemId = pendingCompactBoundaryItemId ?? `claude-compaction-${messageUuid(message, randomUUID2())}`;
|
|
13673
|
+
if (!pendingCompactBoundaryItemId) {
|
|
13674
|
+
pendingCompactSummaryItemId = itemId;
|
|
13675
|
+
}
|
|
13676
|
+
pendingCompactBoundaryItemId = null;
|
|
13677
|
+
upsertCurrentItem(
|
|
13678
|
+
withHistoryItemCreatedAt(
|
|
13679
|
+
claudeContextCompactionItem(itemId, "completed"),
|
|
13680
|
+
sessionMessageTimestamp(message) ?? current?.startedAt
|
|
13681
|
+
)
|
|
13682
|
+
);
|
|
13683
|
+
continue;
|
|
13684
|
+
}
|
|
13576
13685
|
if (isHiddenInitMessage(message.message)) {
|
|
13577
13686
|
skippingHiddenInit = true;
|
|
13578
13687
|
current = null;
|
|
@@ -13592,19 +13701,19 @@ var ClaudeRuntimeAdapter = class extends EventEmitter5 {
|
|
|
13592
13701
|
items: current.items
|
|
13593
13702
|
}));
|
|
13594
13703
|
}
|
|
13595
|
-
const
|
|
13596
|
-
const userStartedAt = isoFromUuidV7(
|
|
13704
|
+
const userMessageUuid = message.uuid ?? randomUUID2();
|
|
13705
|
+
const userStartedAt = isoFromUuidV7(userMessageUuid);
|
|
13597
13706
|
const userItem = await this.userMessageToHistoryItem(
|
|
13598
|
-
|
|
13707
|
+
userMessageUuid,
|
|
13599
13708
|
message.message,
|
|
13600
13709
|
context
|
|
13601
13710
|
);
|
|
13602
13711
|
const stampedUserItem = withHistoryItemCreatedAt(userItem, userStartedAt);
|
|
13603
13712
|
current = {
|
|
13604
|
-
providerTurnId: `claude-turn-${
|
|
13713
|
+
providerTurnId: `claude-turn-${userMessageUuid}`,
|
|
13605
13714
|
startedAt: userStartedAt,
|
|
13606
13715
|
items: [stampedUserItem],
|
|
13607
|
-
itemsById: /* @__PURE__ */ new Map([[
|
|
13716
|
+
itemsById: /* @__PURE__ */ new Map([[userMessageUuid, stampedUserItem]])
|
|
13608
13717
|
};
|
|
13609
13718
|
continue;
|
|
13610
13719
|
}
|
|
@@ -16350,7 +16459,7 @@ var ThreadAuxiliaryStateStore = class {
|
|
|
16350
16459
|
);
|
|
16351
16460
|
return {
|
|
16352
16461
|
id: record.id,
|
|
16353
|
-
kind: "fastMode",
|
|
16462
|
+
kind: record.kind === "goal" ? "goal" : "fastMode",
|
|
16354
16463
|
text: record.text,
|
|
16355
16464
|
createdAt: record.createdAt,
|
|
16356
16465
|
anchorTurnId: record.anchorTurnId ?? fallbackAnchor?.id ?? null
|
|
@@ -16564,13 +16673,10 @@ var ThreadGoalCoordinator = class {
|
|
|
16564
16673
|
}
|
|
16565
16674
|
try {
|
|
16566
16675
|
await this.ensureGoalsFeatureEnabled(record.provider);
|
|
16567
|
-
const
|
|
16676
|
+
const goalHistoryBeforeUpdate = this.listThreadGoalHistory(record.id);
|
|
16677
|
+
const activeGoal = goalHistoryBeforeUpdate.find(
|
|
16568
16678
|
(goal2) => ["active", "paused", "budgetLimited"].includes(goal2.status)
|
|
16569
16679
|
) ?? null;
|
|
16570
|
-
const creatingNewGoal = goalObjectiveChanged(activeGoal, input.objective);
|
|
16571
|
-
if (creatingNewGoal) {
|
|
16572
|
-
markActiveThreadGoalRecordTerminated(this.db, record.id);
|
|
16573
|
-
}
|
|
16574
16680
|
if (input.status === "terminated") {
|
|
16575
16681
|
const terminatedGoal = markActiveThreadGoalRecordTerminated(this.db, record.id);
|
|
16576
16682
|
const goalHistory = this.listThreadGoalHistory(record.id);
|
|
@@ -16581,6 +16687,23 @@ var ThreadGoalCoordinator = class {
|
|
|
16581
16687
|
});
|
|
16582
16688
|
return goal2;
|
|
16583
16689
|
}
|
|
16690
|
+
const startingNewGoal = shouldStartNewGoal(activeGoal, input.objective);
|
|
16691
|
+
if (startingNewGoal && (record.providerTurnId || record.status === "running")) {
|
|
16692
|
+
throw new HttpError(409, {
|
|
16693
|
+
code: "conflict",
|
|
16694
|
+
message: "Interrupt the running turn before replacing this goal."
|
|
16695
|
+
});
|
|
16696
|
+
}
|
|
16697
|
+
if (startingNewGoal && goalHistoryBeforeUpdate.length > 0) {
|
|
16698
|
+
if (!runtime.clearGoal) {
|
|
16699
|
+
throw new HttpError(409, {
|
|
16700
|
+
code: "conflict",
|
|
16701
|
+
message: "This backend cannot safely replace an existing goal."
|
|
16702
|
+
});
|
|
16703
|
+
}
|
|
16704
|
+
await runtime.clearGoal(providerSessionId);
|
|
16705
|
+
markActiveThreadGoalRecordTerminated(this.db, record.id);
|
|
16706
|
+
}
|
|
16584
16707
|
const upstreamStatus = input.status;
|
|
16585
16708
|
const goal = await runtime.setGoal({
|
|
16586
16709
|
providerSessionId,
|
|
@@ -16592,10 +16715,15 @@ var ThreadGoalCoordinator = class {
|
|
|
16592
16715
|
toThreadGoalDtoFromAgentGoal(goal),
|
|
16593
16716
|
record
|
|
16594
16717
|
);
|
|
16595
|
-
const dto =
|
|
16718
|
+
const dto = startingNewGoal ? startFreshGoalLifecycle(upstreamDto) : upstreamDto;
|
|
16596
16719
|
const persistedGoal = toThreadGoalDtoFromRecord(
|
|
16597
|
-
this.persistThreadGoalSnapshot(record.id, dto
|
|
16720
|
+
this.persistThreadGoalSnapshot(record.id, dto, {
|
|
16721
|
+
createNew: startingNewGoal
|
|
16722
|
+
})
|
|
16598
16723
|
);
|
|
16724
|
+
if (startingNewGoal) {
|
|
16725
|
+
this.callbacks.appendGoalActivityNote(record.id, persistedGoal.objective);
|
|
16726
|
+
}
|
|
16599
16727
|
this.callbacks.emitThreadEvent("thread.goal.updated", record.id, {
|
|
16600
16728
|
goal: persistedGoal,
|
|
16601
16729
|
goalHistory: this.listThreadGoalHistory(record.id)
|
|
@@ -16663,12 +16791,13 @@ var ThreadGoalCoordinator = class {
|
|
|
16663
16791
|
throw error;
|
|
16664
16792
|
}
|
|
16665
16793
|
}
|
|
16666
|
-
persistThreadGoalSnapshot(localThreadId, goal) {
|
|
16794
|
+
persistThreadGoalSnapshot(localThreadId, goal, options = {}) {
|
|
16667
16795
|
const dto = "createdAt" in goal && typeof goal.createdAt === "string" ? goal : toThreadGoalDto(goal);
|
|
16668
16796
|
return upsertThreadGoalRecord(this.db, {
|
|
16669
16797
|
threadId: localThreadId,
|
|
16670
16798
|
providerSessionId: dto.threadId,
|
|
16671
16799
|
localGoalId: dto.localGoalId ?? null,
|
|
16800
|
+
createNew: options.createNew ?? false,
|
|
16672
16801
|
objective: dto.objective,
|
|
16673
16802
|
status: dto.status,
|
|
16674
16803
|
tokenBudget: dto.tokenBudget,
|
|
@@ -16791,15 +16920,19 @@ function mergeGoalHistoryEntry(existing, incoming) {
|
|
|
16791
16920
|
function goalHistoryStatusRank(status) {
|
|
16792
16921
|
return ["active", "paused", "budgetLimited"].includes(status) ? 0 : 1;
|
|
16793
16922
|
}
|
|
16794
|
-
function
|
|
16923
|
+
function startFreshGoalLifecycle(goal) {
|
|
16924
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
16795
16925
|
return {
|
|
16796
16926
|
...goal,
|
|
16797
16927
|
tokensUsed: 0,
|
|
16798
|
-
timeUsedSeconds: 0
|
|
16928
|
+
timeUsedSeconds: 0,
|
|
16929
|
+
createdAt: now,
|
|
16930
|
+
updatedAt: now,
|
|
16931
|
+
completedAt: null
|
|
16799
16932
|
};
|
|
16800
16933
|
}
|
|
16801
|
-
function
|
|
16802
|
-
return
|
|
16934
|
+
function shouldStartNewGoal(existing, nextObjective) {
|
|
16935
|
+
return typeof nextObjective === "string" && nextObjective.trim().length > 0 && (existing === null || nextObjective !== existing.objective);
|
|
16803
16936
|
}
|
|
16804
16937
|
function isLocalGoalStatus(status) {
|
|
16805
16938
|
return ["active", "paused", "budgetLimited"].includes(status);
|
|
@@ -18000,6 +18133,14 @@ var ThreadRuntimeEventProjector = class {
|
|
|
18000
18133
|
if (!record) {
|
|
18001
18134
|
return;
|
|
18002
18135
|
}
|
|
18136
|
+
const currentGoal = await callbacks.getThreadGoalAfterRuntimeClear(record);
|
|
18137
|
+
if (currentGoal) {
|
|
18138
|
+
callbacks.emitThreadEvent("thread.goal.updated", record.id, {
|
|
18139
|
+
goal: currentGoal,
|
|
18140
|
+
goalHistory: callbacks.listThreadGoalHistory(record.id)
|
|
18141
|
+
});
|
|
18142
|
+
return;
|
|
18143
|
+
}
|
|
18003
18144
|
markActiveThreadGoalRecordTerminated(db, record.id);
|
|
18004
18145
|
callbacks.emitThreadEvent("thread.goal.cleared", record.id, {
|
|
18005
18146
|
goalHistory: callbacks.listThreadGoalHistory(record.id)
|
|
@@ -22314,7 +22455,11 @@ var ThreadService = class {
|
|
|
22314
22455
|
this.goalCoordinator = new ThreadGoalCoordinator(db, providerFeatures, {
|
|
22315
22456
|
emitThreadEvent: (type, threadId, payload) => this.emitThreadEvent(type, threadId, payload),
|
|
22316
22457
|
requireProviderSessionId: (record) => this.requireProviderSessionId(record),
|
|
22317
|
-
runtimeForProvider: (provider2) => this.runtimeForProvider(provider2)
|
|
22458
|
+
runtimeForProvider: (provider2) => this.runtimeForProvider(provider2),
|
|
22459
|
+
appendGoalActivityNote: (threadId, objective) => this.auxiliaryState.appendActivityNote(threadId, {
|
|
22460
|
+
kind: "goal",
|
|
22461
|
+
text: objective
|
|
22462
|
+
})
|
|
22318
22463
|
});
|
|
22319
22464
|
this.auxiliaryState = new ThreadAuxiliaryStateStore(db, {
|
|
22320
22465
|
cachedTurns: (localThreadId) => this.detailAssembler.cachedTurns(localThreadId),
|
|
@@ -22412,6 +22557,7 @@ var ThreadService = class {
|
|
|
22412
22557
|
resetThreadContextUsage: (localThreadId, emitEvent) => this.resetThreadContextUsage(localThreadId, emitEvent),
|
|
22413
22558
|
setThreadContextUsage: (localThreadId, usage, emitEvent) => this.setThreadContextUsage(localThreadId, usage, emitEvent),
|
|
22414
22559
|
getThreadContextUsage: (localThreadId) => this.getThreadContextUsage(localThreadId),
|
|
22560
|
+
getThreadGoalAfterRuntimeClear: (record) => this.goalCoordinator.getThreadGoalForRecord(record),
|
|
22415
22561
|
toThreadGoalDtoFromAgentGoal: (goal) => this.goalCoordinator.toThreadGoalDtoFromAgentGoal(goal),
|
|
22416
22562
|
toThreadGoalDtoFromRecord: (record) => this.goalCoordinator.toThreadGoalDtoFromRecord(record)
|
|
22417
22563
|
}
|