@sentry/junior 0.177.0 → 0.179.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/{agent-hooks-IYHCN7CO.js → agent-hooks-32IDCLMW.js} +12 -12
- package/dist/api/conversations/archive.d.ts +5 -2
- package/dist/api/conversations/list.d.ts +0 -1
- package/dist/api/conversations/membership.d.ts +14 -0
- package/dist/api/conversations/projection.d.ts +2 -1
- package/dist/api/locations/detail.d.ts +1 -1
- package/dist/api/people/shared.d.ts +2 -2
- package/dist/api/schema/conversation.d.ts +4 -4
- package/dist/api/schema/location.d.ts +1 -1
- package/dist/api/schema/person.d.ts +1 -1
- package/dist/api/schema.js +1 -1
- package/dist/api.js +399 -287
- package/dist/app.js +573 -198
- package/dist/{catalog-runtime-2TD4CXSB.js → catalog-runtime-FBVA6WAQ.js} +5 -5
- package/dist/chat/agent/sandbox.d.ts +2 -0
- package/dist/chat/conversations/sql/participants.d.ts +1 -0
- package/dist/chat/conversations/store.d.ts +0 -1
- package/dist/chat/sandbox/errors.d.ts +4 -0
- package/dist/chat/sandbox/noninteractive-command.d.ts +1 -0
- package/dist/chat/sandbox/prepare-workspace.d.ts +27 -3
- package/dist/chat/sandbox/sandbox.d.ts +2 -0
- package/dist/chat/sandbox/session.d.ts +2 -0
- package/dist/chat/sandbox/snapshot/builder-sandbox.d.ts +5 -0
- package/dist/chat/sandbox/snapshot/install.d.ts +2 -2
- package/dist/chat/sandbox/snapshot/span.d.ts +3 -0
- package/dist/chat/sandbox/snapshot/store.d.ts +20 -17
- package/dist/chat/sandbox/snapshot/waiting-error.d.ts +15 -0
- package/dist/chat/sandbox/snapshot/workspace.d.ts +17 -0
- package/dist/chat/scheduled-tasks/personal.d.ts +5 -8
- package/dist/chat/scheduled-tasks/runs.d.ts +49 -0
- package/dist/chat/scheduled-tasks/schedule-intent.d.ts +1 -1
- package/dist/chat/scheduled-tasks/tasks.d.ts +27 -0
- package/dist/chat/scheduled-tasks/tool-support.d.ts +0 -4
- package/dist/chat/scheduled-tasks/tools/create.d.ts +1 -1
- package/dist/chat/scheduled-tasks/tools/update.d.ts +1 -1
- package/dist/chat/scheduled-tasks/types.d.ts +127 -78
- package/dist/chat/services/workspace-snapshot-wait.d.ts +27 -0
- package/dist/chat/workspaces/types.d.ts +1 -0
- package/dist/{chunk-CAWBLVBY.js → chunk-2HIPYNQI.js} +6 -6
- package/dist/{chunk-ZCYABD7N.js → chunk-3AFVR5UC.js} +5 -5
- package/dist/{chunk-PA7QDS7P.js → chunk-3I7XE3PA.js} +204 -100
- package/dist/{chunk-VGDOEKO2.js → chunk-3TM3FYLQ.js} +1 -1
- package/dist/{chunk-6NZRKJWU.js → chunk-4GWXAILB.js} +1 -1
- package/dist/{chunk-LFZODSAY.js → chunk-5IRROPA5.js} +4 -4
- package/dist/{chunk-N4YEVOEC.js → chunk-B6ECUSVX.js} +45 -276
- package/dist/{chunk-4TWDRU64.js → chunk-CJLBLELR.js} +5 -5
- package/dist/{chunk-3FWXQZJJ.js → chunk-FAS6AIMF.js} +1 -1
- package/dist/{chunk-I7PKGMKI.js → chunk-FIDMHAO6.js} +6 -6
- package/dist/{chunk-4SVAFAI2.js → chunk-FTGD2FBP.js} +1 -1
- package/dist/{chunk-LVL5NG5G.js → chunk-G2CAC4CV.js} +19 -19
- package/dist/{chunk-DLTP6TUJ.js → chunk-GENV6KF2.js} +2 -2
- package/dist/{chunk-FVVYHML7.js → chunk-GPLAT6H7.js} +6 -6
- package/dist/{chunk-7CVYXUOJ.js → chunk-HQNE5APD.js} +1 -1
- package/dist/{chunk-Q362TECJ.js → chunk-JMJNFEDC.js} +331 -2
- package/dist/{chunk-AFOFVEXP.js → chunk-JT4WMD3C.js} +124 -13
- package/dist/{chunk-7ZFESKJ2.js → chunk-KJLHIHFF.js} +9 -9
- package/dist/{chunk-QLLOX7JY.js → chunk-Q5B3WTGG.js} +8 -8
- package/dist/{chunk-DGGIKWYQ.js → chunk-R5NWWYIS.js} +1 -1
- package/dist/{chunk-WCQUKX74.js → chunk-R6U7N4FS.js} +14 -5
- package/dist/{chunk-WYO2HXY5.js → chunk-RTRBYSF3.js} +1 -1
- package/dist/{chunk-2IMQLZHS.js → chunk-RZRYOLCE.js} +1144 -136
- package/dist/{chunk-7B6NW4P7.js → chunk-SLGGOASN.js} +1 -1
- package/dist/{chunk-SNPQGTHJ.js → chunk-TIEKUH4Q.js} +1 -1
- package/dist/{chunk-ELEAIRTG.js → chunk-TKB25A3L.js} +2 -2
- package/dist/{chunk-UB5YB2ED.js → chunk-U4TKB6F6.js} +6 -6
- package/dist/{chunk-ORMJY4RG.js → chunk-UT6X7XN7.js} +1 -1
- package/dist/{chunk-LPMLAJYR.js → chunk-VP4OVRDD.js} +11 -17
- package/dist/{chunk-7PZDAXIK.js → chunk-XSPSCWLH.js} +1 -1
- package/dist/chunk-XZY6G5YZ.js +1088 -0
- package/dist/{chunk-J2EWL6GJ.js → chunk-YFNAMRGE.js} +1 -1
- package/dist/{chunk-6KGMMVHF.js → chunk-ZQMG3CL5.js} +2 -2
- package/dist/cli/chat.js +31 -30
- package/dist/cli/check.js +6 -6
- package/dist/cli/plugins.js +13 -13
- package/dist/cli/snapshot-create.js +10 -10
- package/dist/cli/upgrade.js +7 -7
- package/dist/db/schema/conversation-participants.d.ts +17 -0
- package/dist/db/schema/conversations.d.ts +629 -1
- package/dist/db/schema/scheduled-tasks.d.ts +29 -5
- package/dist/db/schema/snapshots.d.ts +1 -1
- package/dist/db/schema.d.ts +46 -5
- package/dist/{db-A3CB76US.js → db-EJAV7JL6.js} +7 -7
- package/dist/instrumentation.js +4 -4
- package/dist/nitro.js +2 -2
- package/dist/{oauth-callback-server-64QMI3IB.js → oauth-callback-server-GAVSUBTQ.js} +23 -23
- package/dist/{runner-EIAE2BGD.js → runner-34JL64JF.js} +15 -15
- package/dist/{skills-ZADWFKDG.js → skills-U545HALM.js} +6 -6
- package/dist/spawn-I2S4IDUQ.js +37 -0
- package/dist/{validation-4I6AF4CV.js → validation-EVXVGA44.js} +6 -6
- package/dist/version.js +1 -1
- package/dist/{work-BS6BZ66U.js → work-QOAUPDBU.js} +18 -18
- package/dist/worker-5GX2IKLZ.js +24 -0
- package/migrations/0033_workspace_snapshot_active_build.sql +1 -0
- package/migrations/0034_strong_randall.sql +31 -0
- package/migrations/meta/0033_snapshot.json +2914 -0
- package/migrations/meta/0034_snapshot.json +3038 -0
- package/migrations/meta/_journal.json +15 -1
- package/package.json +4 -4
- package/dist/chat/scheduled-tasks/store.d.ts +0 -78
- package/dist/chunk-4TRHPYTL.js +0 -1944
- package/dist/spawn-DVUOUFFC.js +0 -37
- package/dist/worker-RS5PWTCE.js +0 -24
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
PluginCredentialFailureError,
|
|
8
8
|
agentNameSchema,
|
|
9
9
|
createPluginAuthOrchestration
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-3AFVR5UC.js";
|
|
11
11
|
import {
|
|
12
12
|
authorizationId
|
|
13
13
|
} from "./chunk-DIB4GX3G.js";
|
|
@@ -32,7 +32,10 @@ import {
|
|
|
32
32
|
searchSlackUsers,
|
|
33
33
|
selectPluginGrant,
|
|
34
34
|
toMcpProviderError
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-Q5B3WTGG.js";
|
|
36
|
+
import {
|
|
37
|
+
getTurnRequestDeadline
|
|
38
|
+
} from "./chunk-4GWXAILB.js";
|
|
36
39
|
import {
|
|
37
40
|
RetryableDeliveryError,
|
|
38
41
|
actorFromRun,
|
|
@@ -48,7 +51,7 @@ import {
|
|
|
48
51
|
getSlackMessagePermalink,
|
|
49
52
|
resolveBaseUrl,
|
|
50
53
|
uploadFilesToConversation
|
|
51
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-2HIPYNQI.js";
|
|
52
55
|
import {
|
|
53
56
|
parseSandboxEgressAuthRequiredSignal,
|
|
54
57
|
parseSandboxEgressPermissionDeniedSignal,
|
|
@@ -59,7 +62,7 @@ import {
|
|
|
59
62
|
SCHEDULED_TASK_SYSTEM_ACTOR,
|
|
60
63
|
compactEventTask,
|
|
61
64
|
createEventTask,
|
|
62
|
-
|
|
65
|
+
createScheduledTask,
|
|
63
66
|
deleteEventTask,
|
|
64
67
|
eventNamespaceSchema,
|
|
65
68
|
eventTaskListToolResultSchema,
|
|
@@ -67,30 +70,29 @@ import {
|
|
|
67
70
|
eventTaskSuccess,
|
|
68
71
|
eventTaskToolResultSchema,
|
|
69
72
|
getEventTask,
|
|
70
|
-
getFirstRunAtMs,
|
|
71
73
|
getResourceEventCatalog,
|
|
72
|
-
getZonedDateTimeParts,
|
|
73
74
|
incrementStat,
|
|
74
75
|
injectContinuationToolName,
|
|
75
76
|
juniorToolOutputSchema,
|
|
76
77
|
listEventTasksForTeam,
|
|
78
|
+
listScheduledTasksForTeam,
|
|
77
79
|
makeStructuredToolOutput,
|
|
78
80
|
normalizeEventIdentifier,
|
|
79
81
|
pluginSupportsEvent,
|
|
82
|
+
readScheduledTask,
|
|
80
83
|
registeredEventTaskTriggerSchema,
|
|
81
84
|
registeredEventTypeSchema,
|
|
82
85
|
registeredResourceTypeSchema,
|
|
83
86
|
requireEventTaskSlackContext,
|
|
84
87
|
requireSupportedEventTaskTrigger,
|
|
85
|
-
resolveLocalScheduleAtMs,
|
|
86
88
|
saveEventTask,
|
|
87
|
-
|
|
89
|
+
saveScheduledTask,
|
|
88
90
|
storeAttachments,
|
|
89
91
|
writableEventTask
|
|
90
|
-
} from "./chunk-
|
|
92
|
+
} from "./chunk-XZY6G5YZ.js";
|
|
91
93
|
import {
|
|
92
94
|
parseSlackThreadId
|
|
93
|
-
} from "./chunk-
|
|
95
|
+
} from "./chunk-FAS6AIMF.js";
|
|
94
96
|
import {
|
|
95
97
|
AGENTS_REMOVAL_NOTICE,
|
|
96
98
|
AGENTS_REPLACEMENT_NOTICE,
|
|
@@ -160,23 +162,18 @@ import {
|
|
|
160
162
|
stripRuntimeTurnContext,
|
|
161
163
|
trimTrailingAssistantMessages,
|
|
162
164
|
truncateText
|
|
163
|
-
} from "./chunk-
|
|
165
|
+
} from "./chunk-FIDMHAO6.js";
|
|
164
166
|
import {
|
|
165
167
|
bindSandboxFileSystem,
|
|
166
168
|
createSandboxSession,
|
|
169
|
+
dependencies,
|
|
167
170
|
getSandboxResources,
|
|
168
|
-
getVercelSandboxCredentials,
|
|
169
|
-
isAlreadyExistsError,
|
|
170
171
|
isMissingError,
|
|
171
|
-
|
|
172
|
-
isSandboxMissingError,
|
|
173
|
-
isSandboxUnavailableError,
|
|
174
|
-
isSnapshottingError,
|
|
172
|
+
postinstall,
|
|
175
173
|
resolve,
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
} from "./chunk-N4YEVOEC.js";
|
|
174
|
+
setSnapshotSpanAttributes,
|
|
175
|
+
stopSession
|
|
176
|
+
} from "./chunk-B6ECUSVX.js";
|
|
180
177
|
import {
|
|
181
178
|
privateTraceResultAttributes
|
|
182
179
|
} from "./chunk-UIE3R5XU.js";
|
|
@@ -196,6 +193,7 @@ import {
|
|
|
196
193
|
createResourceEventSubscription,
|
|
197
194
|
createWorkspace,
|
|
198
195
|
deleteWorkspace,
|
|
196
|
+
deleteWorkspaceSnapshotBuilders,
|
|
199
197
|
downloadPrivateSlackFile,
|
|
200
198
|
getDashboardTaskLink,
|
|
201
199
|
getFilePermalink,
|
|
@@ -207,19 +205,23 @@ import {
|
|
|
207
205
|
getSlackToolContext,
|
|
208
206
|
getWorkspace,
|
|
209
207
|
getWorkspaceByName,
|
|
208
|
+
getWorkspaceSnapshotBuilder,
|
|
209
|
+
invalidateReadySnapshot,
|
|
210
210
|
isConversationScopedChannel,
|
|
211
211
|
listResourceEventSubscriptions,
|
|
212
212
|
listWorkspaces,
|
|
213
|
+
loadSnapshotsForProfile,
|
|
213
214
|
normalizeSlackConversationId,
|
|
214
215
|
readActorIdentity,
|
|
215
|
-
recordResolvedWorkspaceSnapshot,
|
|
216
216
|
requireResourceWatchConversation,
|
|
217
217
|
resolveChannelCapabilities,
|
|
218
|
+
setWorkspaceSnapshot,
|
|
219
|
+
setWorkspaceSnapshotBuild,
|
|
218
220
|
tryWorkspaceRepoCheckoutPath,
|
|
219
221
|
updateWorkspace,
|
|
220
222
|
withSlackRetries,
|
|
221
223
|
workspaceRepoCheckoutPath
|
|
222
|
-
} from "./chunk-
|
|
224
|
+
} from "./chunk-3I7XE3PA.js";
|
|
223
225
|
import {
|
|
224
226
|
KNOWN_CONVERSATION_EVENT_TYPES,
|
|
225
227
|
contextProvenance,
|
|
@@ -231,29 +233,48 @@ import {
|
|
|
231
233
|
instructionActors,
|
|
232
234
|
instructionProvenanceFor,
|
|
233
235
|
sameActorIdentity
|
|
234
|
-
} from "./chunk-
|
|
236
|
+
} from "./chunk-VP4OVRDD.js";
|
|
235
237
|
import {
|
|
236
238
|
juniorArtifacts,
|
|
237
|
-
juniorIdentities
|
|
238
|
-
|
|
239
|
+
juniorIdentities,
|
|
240
|
+
juniorSchedulerRuns
|
|
241
|
+
} from "./chunk-R6U7N4FS.js";
|
|
239
242
|
import {
|
|
240
243
|
SANDBOX_DATA_ROOT,
|
|
241
244
|
SANDBOX_REPOS_ROOT,
|
|
242
245
|
SANDBOX_SKILLS_ROOT,
|
|
243
246
|
SANDBOX_WORKSPACE_ROOT,
|
|
247
|
+
WorkspaceSnapshotWaitingError,
|
|
244
248
|
buildCommandScript,
|
|
245
249
|
buildNonInteractiveShellScript,
|
|
250
|
+
create,
|
|
246
251
|
ensureDockerDaemon,
|
|
252
|
+
getVercelSandboxCredentials,
|
|
247
253
|
hash,
|
|
254
|
+
isAbortError,
|
|
255
|
+
isAlreadyExistsError,
|
|
256
|
+
isSandboxApiTransientError,
|
|
257
|
+
isSandboxCommandStreamInterruptedError,
|
|
258
|
+
isSandboxMissingError,
|
|
259
|
+
isSandboxUnavailableError,
|
|
260
|
+
isSnapshottingError,
|
|
261
|
+
isStale,
|
|
262
|
+
isWorkspaceSnapshotWaitingError,
|
|
263
|
+
isWorkspaceSnapshotWaitingResult,
|
|
248
264
|
runNonInteractiveCommand,
|
|
249
265
|
sandboxSkillDir,
|
|
250
266
|
sandboxSkillFile,
|
|
251
267
|
sandboxSkillPathResolution,
|
|
252
|
-
sleep
|
|
253
|
-
|
|
268
|
+
sleep,
|
|
269
|
+
throwSandboxOperationError,
|
|
270
|
+
workspaceNameFromWaitingResult,
|
|
271
|
+
wrapSandboxSetupError
|
|
272
|
+
} from "./chunk-JMJNFEDC.js";
|
|
254
273
|
import {
|
|
255
|
-
|
|
256
|
-
|
|
274
|
+
fenceLock,
|
|
275
|
+
getStateAdapter,
|
|
276
|
+
withLock
|
|
277
|
+
} from "./chunk-3TM3FYLQ.js";
|
|
257
278
|
import {
|
|
258
279
|
ACTIVE_TURN_COMPACTION_SUMMARY_PREFIX,
|
|
259
280
|
COMPACTION_SUMMARY_PREFIX,
|
|
@@ -286,7 +307,7 @@ import {
|
|
|
286
307
|
resolveGatewayCredential,
|
|
287
308
|
resolveGatewayModel,
|
|
288
309
|
unwrapCurrentInstruction
|
|
289
|
-
} from "./chunk-
|
|
310
|
+
} from "./chunk-GENV6KF2.js";
|
|
290
311
|
import {
|
|
291
312
|
canExposeConversationPayload,
|
|
292
313
|
resolveConversationPrivacy,
|
|
@@ -296,16 +317,16 @@ import {
|
|
|
296
317
|
toGenAiMessagesTraceAttributes,
|
|
297
318
|
toGenAiPayloadMetadata,
|
|
298
319
|
toGenAiPayloadTraceAttributes
|
|
299
|
-
} from "./chunk-
|
|
320
|
+
} from "./chunk-XSPSCWLH.js";
|
|
300
321
|
import {
|
|
301
322
|
discoverSkills,
|
|
302
323
|
findSkillByName,
|
|
303
324
|
loadSkillsByName,
|
|
304
325
|
parseSkillInvocation
|
|
305
|
-
} from "./chunk-
|
|
326
|
+
} from "./chunk-ZQMG3CL5.js";
|
|
306
327
|
import {
|
|
307
328
|
pluginCatalogRuntime
|
|
308
|
-
} from "./chunk-
|
|
329
|
+
} from "./chunk-YFNAMRGE.js";
|
|
309
330
|
import {
|
|
310
331
|
CredentialUnavailableError,
|
|
311
332
|
OAuthProviderError,
|
|
@@ -313,7 +334,7 @@ import {
|
|
|
313
334
|
getOAuthProviderErrorAttributes,
|
|
314
335
|
resolveAuthTokenPlaceholder,
|
|
315
336
|
resolvePluginCommandEnv
|
|
316
|
-
} from "./chunk-
|
|
337
|
+
} from "./chunk-SLGGOASN.js";
|
|
317
338
|
import {
|
|
318
339
|
credentialUserSubjectId
|
|
319
340
|
} from "./chunk-CGA4ERJ5.js";
|
|
@@ -343,7 +364,7 @@ import {
|
|
|
343
364
|
summarizeMessageText,
|
|
344
365
|
withLogContext,
|
|
345
366
|
withSpan
|
|
346
|
-
} from "./chunk-
|
|
367
|
+
} from "./chunk-HQNE5APD.js";
|
|
347
368
|
import {
|
|
348
369
|
startInactiveSpan,
|
|
349
370
|
withActiveSpan
|
|
@@ -987,6 +1008,97 @@ function nextEmptyOutputContinuation(args) {
|
|
|
987
1008
|
return isContinuablePiBoundary(messages) ? { kind: "retry", messages } : { kind: "exhausted" };
|
|
988
1009
|
}
|
|
989
1010
|
|
|
1011
|
+
// src/chat/services/workspace-snapshot-wait.ts
|
|
1012
|
+
import { randomUUID } from "crypto";
|
|
1013
|
+
var SWITCH_WORKSPACE_TOOL = "switchWorkspace";
|
|
1014
|
+
function pendingWorkspaceSnapshotWait(messages) {
|
|
1015
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
1016
|
+
const message = messages[index];
|
|
1017
|
+
if (!isToolResultMessage(message)) break;
|
|
1018
|
+
if (message.toolName !== SWITCH_WORKSPACE_TOOL) continue;
|
|
1019
|
+
const name = workspaceNameFromWaitingResult(message.details);
|
|
1020
|
+
return name ? { name } : null;
|
|
1021
|
+
}
|
|
1022
|
+
return null;
|
|
1023
|
+
}
|
|
1024
|
+
async function continueWorkspaceSnapshotWait(args) {
|
|
1025
|
+
if (!isContinuablePiBoundary(args.messages)) return { kind: "none" };
|
|
1026
|
+
const pending = pendingWorkspaceSnapshotWait(args.messages);
|
|
1027
|
+
if (!pending) return { kind: "none" };
|
|
1028
|
+
const tool = args.tools.find((entry) => entry.name === SWITCH_WORKSPACE_TOOL);
|
|
1029
|
+
if (!tool) return { kind: "none" };
|
|
1030
|
+
const toolCallId = randomUUID();
|
|
1031
|
+
const assistantMessage = {
|
|
1032
|
+
role: "assistant",
|
|
1033
|
+
api: "openai-responses",
|
|
1034
|
+
provider: "openai",
|
|
1035
|
+
model: "host-continue",
|
|
1036
|
+
content: [
|
|
1037
|
+
{
|
|
1038
|
+
type: "toolCall",
|
|
1039
|
+
id: toolCallId,
|
|
1040
|
+
name: SWITCH_WORKSPACE_TOOL,
|
|
1041
|
+
arguments: { name: pending.name }
|
|
1042
|
+
}
|
|
1043
|
+
],
|
|
1044
|
+
usage: {
|
|
1045
|
+
input: 0,
|
|
1046
|
+
output: 0,
|
|
1047
|
+
cacheRead: 0,
|
|
1048
|
+
cacheWrite: 0,
|
|
1049
|
+
totalTokens: 0,
|
|
1050
|
+
cost: {
|
|
1051
|
+
input: 0,
|
|
1052
|
+
output: 0,
|
|
1053
|
+
cacheRead: 0,
|
|
1054
|
+
cacheWrite: 0,
|
|
1055
|
+
total: 0
|
|
1056
|
+
}
|
|
1057
|
+
},
|
|
1058
|
+
timestamp: Date.now(),
|
|
1059
|
+
stopReason: "toolUse"
|
|
1060
|
+
};
|
|
1061
|
+
let result;
|
|
1062
|
+
try {
|
|
1063
|
+
result = await tool.execute(
|
|
1064
|
+
toolCallId,
|
|
1065
|
+
{ name: pending.name },
|
|
1066
|
+
args.signal
|
|
1067
|
+
);
|
|
1068
|
+
} catch (error) {
|
|
1069
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1070
|
+
const toolResultMessage2 = {
|
|
1071
|
+
role: "toolResult",
|
|
1072
|
+
toolCallId,
|
|
1073
|
+
toolName: SWITCH_WORKSPACE_TOOL,
|
|
1074
|
+
content: [{ type: "text", text: message }],
|
|
1075
|
+
details: { error: message },
|
|
1076
|
+
isError: true,
|
|
1077
|
+
timestamp: Date.now()
|
|
1078
|
+
};
|
|
1079
|
+
return {
|
|
1080
|
+
kind: "failed",
|
|
1081
|
+
error,
|
|
1082
|
+
messages: [...args.messages, assistantMessage, toolResultMessage2]
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
const toolResultMessage = {
|
|
1086
|
+
role: "toolResult",
|
|
1087
|
+
toolCallId,
|
|
1088
|
+
toolName: SWITCH_WORKSPACE_TOOL,
|
|
1089
|
+
content: result.content,
|
|
1090
|
+
details: result.details,
|
|
1091
|
+
isError: false,
|
|
1092
|
+
timestamp: Date.now()
|
|
1093
|
+
};
|
|
1094
|
+
const messages = [
|
|
1095
|
+
...args.messages,
|
|
1096
|
+
assistantMessage,
|
|
1097
|
+
toolResultMessage
|
|
1098
|
+
];
|
|
1099
|
+
return isWorkspaceSnapshotWaitingResult(result.details) ? { kind: "waiting", messages } : { kind: "ready", messages };
|
|
1100
|
+
}
|
|
1101
|
+
|
|
990
1102
|
// src/chat/agent/retry-usage.ts
|
|
991
1103
|
function getDiscardedRetryUsage(messages, retryMessages) {
|
|
992
1104
|
const keepsExactPrefix = retryMessages.length <= messages.length && retryMessages.every((message, index) => message === messages[index]);
|
|
@@ -2671,7 +2783,7 @@ function formatSandboxCommandResult(params) {
|
|
|
2671
2783
|
import fs4 from "fs/promises";
|
|
2672
2784
|
|
|
2673
2785
|
// src/chat/sandbox/egress/session.ts
|
|
2674
|
-
import { createHmac, randomUUID, timingSafeEqual } from "crypto";
|
|
2786
|
+
import { createHmac, randomUUID as randomUUID2, timingSafeEqual } from "crypto";
|
|
2675
2787
|
var SANDBOX_EGRESS_PROXY_PATH = "/api/internal/sandbox-egress";
|
|
2676
2788
|
var SANDBOX_EGRESS_TOKEN_VERSION = "v1";
|
|
2677
2789
|
var SANDBOX_EGRESS_HMAC_CONTEXT = "junior.sandbox_egress.v1";
|
|
@@ -2743,7 +2855,7 @@ function createSandboxEgressCredentialToken(input) {
|
|
|
2743
2855
|
credentials: input.credentials,
|
|
2744
2856
|
egressId: input.egressId,
|
|
2745
2857
|
expiresAtMs: now + ttlMs2,
|
|
2746
|
-
contextId:
|
|
2858
|
+
contextId: randomUUID2()
|
|
2747
2859
|
};
|
|
2748
2860
|
const payload = `${SANDBOX_EGRESS_TOKEN_VERSION}.${base64Url(
|
|
2749
2861
|
JSON.stringify(context)
|
|
@@ -2960,11 +3072,11 @@ async function resolveSandboxCommandEnvironment() {
|
|
|
2960
3072
|
}
|
|
2961
3073
|
|
|
2962
3074
|
// src/chat/sandbox/session.ts
|
|
2963
|
-
import { randomUUID as
|
|
2964
|
-
import { Sandbox } from "@vercel/sandbox";
|
|
3075
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
3076
|
+
import { Sandbox as Sandbox2 } from "@vercel/sandbox";
|
|
2965
3077
|
|
|
2966
3078
|
// src/chat/sandbox/prepare-workspace.ts
|
|
2967
|
-
async function
|
|
3079
|
+
async function prepareWorkspaceRepositories(params) {
|
|
2968
3080
|
const { sandbox, workspace, signal } = params;
|
|
2969
3081
|
signal?.throwIfAborted();
|
|
2970
3082
|
await params.applyNetworkPolicy(sandbox);
|
|
@@ -2972,15 +3084,54 @@ async function prepareWorkspaceSnapshot(params) {
|
|
|
2972
3084
|
if (params.removeCredentialRoute) {
|
|
2973
3085
|
await sandbox.update({ networkPolicy: "allow-all" });
|
|
2974
3086
|
}
|
|
2975
|
-
|
|
2976
|
-
|
|
3087
|
+
}
|
|
3088
|
+
function workspaceSetupCommand(workspace) {
|
|
3089
|
+
return {
|
|
2977
3090
|
cmd: "bash",
|
|
2978
3091
|
args: ["-euo", "pipefail", "-c", workspace.setupScript],
|
|
2979
3092
|
cwd: SANDBOX_WORKSPACE_ROOT,
|
|
2980
3093
|
env: {
|
|
2981
3094
|
JUNIOR_REPOS_ROOT: SANDBOX_REPOS_ROOT,
|
|
2982
3095
|
JUNIOR_WORKSPACE_ROOT: SANDBOX_WORKSPACE_ROOT
|
|
2983
|
-
}
|
|
3096
|
+
}
|
|
3097
|
+
};
|
|
3098
|
+
}
|
|
3099
|
+
function workspaceSnapshotSetupCommand(workspace, buildId) {
|
|
3100
|
+
const stateRoot = `/tmp/junior-snapshot-build-${buildId}`;
|
|
3101
|
+
const wrapper = [
|
|
3102
|
+
`mkdir -p "${stateRoot}"`,
|
|
3103
|
+
`exec 9>"${stateRoot}/lock"`,
|
|
3104
|
+
"flock 9",
|
|
3105
|
+
`if test -f "${stateRoot}/exit"; then exit "$(cat "${stateRoot}/exit")"; fi`,
|
|
3106
|
+
"set +e",
|
|
3107
|
+
'bash -euo pipefail -c "$1"',
|
|
3108
|
+
"status=$?",
|
|
3109
|
+
`printf '%s\\n' "$status" > "${stateRoot}/exit"`,
|
|
3110
|
+
'exit "$status"'
|
|
3111
|
+
].join("\n");
|
|
3112
|
+
return {
|
|
3113
|
+
cmd: "bash",
|
|
3114
|
+
args: [
|
|
3115
|
+
"-euo",
|
|
3116
|
+
"pipefail",
|
|
3117
|
+
"-c",
|
|
3118
|
+
wrapper,
|
|
3119
|
+
"junior-setup",
|
|
3120
|
+
workspace.setupScript
|
|
3121
|
+
],
|
|
3122
|
+
cwd: SANDBOX_WORKSPACE_ROOT,
|
|
3123
|
+
env: {
|
|
3124
|
+
JUNIOR_REPOS_ROOT: SANDBOX_REPOS_ROOT,
|
|
3125
|
+
JUNIOR_WORKSPACE_ROOT: SANDBOX_WORKSPACE_ROOT
|
|
3126
|
+
}
|
|
3127
|
+
};
|
|
3128
|
+
}
|
|
3129
|
+
async function prepareWorkspaceSnapshot(params) {
|
|
3130
|
+
const { sandbox, workspace, signal } = params;
|
|
3131
|
+
await prepareWorkspaceRepositories(params);
|
|
3132
|
+
if (!workspace.setupScript.trim()) return;
|
|
3133
|
+
const result = await sandbox.runCommand({
|
|
3134
|
+
...workspaceSetupCommand(workspace),
|
|
2984
3135
|
signal
|
|
2985
3136
|
});
|
|
2986
3137
|
if (result.exitCode !== 0) {
|
|
@@ -2990,6 +3141,486 @@ async function prepareWorkspaceSnapshot(params) {
|
|
|
2990
3141
|
}
|
|
2991
3142
|
}
|
|
2992
3143
|
|
|
3144
|
+
// src/chat/sandbox/snapshot/workspace.ts
|
|
3145
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
3146
|
+
import { Sandbox } from "@vercel/sandbox";
|
|
3147
|
+
var BUILD_TIMEOUT_MS = 60 * 60 * 1e3;
|
|
3148
|
+
var BUILD_TIMEOUT_ERROR = "Workspace snapshot build timed out after 1 hour";
|
|
3149
|
+
var BUILD_LOCK_TTL_MS = 2 * 60 * 1e3;
|
|
3150
|
+
var BUILD_LOCK_PREFIX = "junior:workspace_snapshot_start";
|
|
3151
|
+
var WAIT_POLL_MS = 5e3;
|
|
3152
|
+
var TRANSIENT_API_RETRY_MS = 2e3;
|
|
3153
|
+
var BUILD_COMMAND_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
3154
|
+
var BUILD_COMMAND_TIMEOUT_ERROR = "Workspace snapshot build phase timed out after 10 minutes";
|
|
3155
|
+
var COMMAND_STATUS_POLL_TIMEOUT_MS = 1e3;
|
|
3156
|
+
var WAIT_YIELD_BUFFER_MS = 4e4;
|
|
3157
|
+
function isCancelOrTransient(error) {
|
|
3158
|
+
return isAbortError(error) || isSandboxApiTransientError(error);
|
|
3159
|
+
}
|
|
3160
|
+
function buildLockKey(profileHash) {
|
|
3161
|
+
return `${BUILD_LOCK_PREFIX}:${profileHash}`;
|
|
3162
|
+
}
|
|
3163
|
+
function builderName() {
|
|
3164
|
+
return `junior-ws-${randomUUID3()}`;
|
|
3165
|
+
}
|
|
3166
|
+
async function snapshotFromSql(workspaceId, value, staleSnapshotId) {
|
|
3167
|
+
const { ready } = await loadSnapshotsForProfile(
|
|
3168
|
+
getDb(),
|
|
3169
|
+
workspaceId,
|
|
3170
|
+
value.hash
|
|
3171
|
+
);
|
|
3172
|
+
if (!ready) return null;
|
|
3173
|
+
if (ready.id === staleSnapshotId || isStale(value, ready.generatedAt.getTime())) {
|
|
3174
|
+
const ownerName = await invalidateReadySnapshot({
|
|
3175
|
+
workspaceId,
|
|
3176
|
+
profileHash: value.hash,
|
|
3177
|
+
snapshotId: ready.id
|
|
3178
|
+
});
|
|
3179
|
+
try {
|
|
3180
|
+
await deleteBuilder(ownerName);
|
|
3181
|
+
} catch (error) {
|
|
3182
|
+
logException(error, "sandbox.workspace_snapshot.builder.delete_failed", {
|
|
3183
|
+
"app.workspace.id": workspaceId
|
|
3184
|
+
});
|
|
3185
|
+
}
|
|
3186
|
+
return null;
|
|
3187
|
+
}
|
|
3188
|
+
return {
|
|
3189
|
+
snapshotId: ready.id,
|
|
3190
|
+
profileHash: ready.profileHash,
|
|
3191
|
+
dependencyCount: value.dependencyCount,
|
|
3192
|
+
cacheHit: true,
|
|
3193
|
+
resolveOutcome: "cache_hit",
|
|
3194
|
+
createdAtMs: ready.generatedAt.getTime(),
|
|
3195
|
+
buildDurationMs: ready.buildDurationMs
|
|
3196
|
+
};
|
|
3197
|
+
}
|
|
3198
|
+
function activeBuild(build) {
|
|
3199
|
+
if (!build || build.status !== "building" || !build.sandboxName) return null;
|
|
3200
|
+
return build;
|
|
3201
|
+
}
|
|
3202
|
+
async function deleteBuilder(sandboxName) {
|
|
3203
|
+
if (!sandboxName) return;
|
|
3204
|
+
await deleteWorkspaceSnapshotBuilders([sandboxName]);
|
|
3205
|
+
}
|
|
3206
|
+
function workspaceDeletedError(workspace) {
|
|
3207
|
+
return new Error(
|
|
3208
|
+
`Workspace ${workspace.name} was deleted while its snapshot was building`
|
|
3209
|
+
);
|
|
3210
|
+
}
|
|
3211
|
+
function requireBuildWrite(written, workspace) {
|
|
3212
|
+
if (!written) {
|
|
3213
|
+
throw new Error(
|
|
3214
|
+
`Workspace ${workspace.name} snapshot build lost SQL ownership`
|
|
3215
|
+
);
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
async function markFailed(workspace, build, error, beforeWrite) {
|
|
3219
|
+
try {
|
|
3220
|
+
await beforeWrite();
|
|
3221
|
+
const written = await setWorkspaceSnapshotBuild(workspace.id, {
|
|
3222
|
+
...build,
|
|
3223
|
+
status: "failed",
|
|
3224
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3225
|
+
});
|
|
3226
|
+
requireBuildWrite(written, workspace);
|
|
3227
|
+
} catch (persistError) {
|
|
3228
|
+
logException(
|
|
3229
|
+
persistError,
|
|
3230
|
+
"sandbox.workspace_snapshot.failure_persist_failed",
|
|
3231
|
+
{ "app.workspace.id": workspace.id }
|
|
3232
|
+
);
|
|
3233
|
+
return;
|
|
3234
|
+
}
|
|
3235
|
+
try {
|
|
3236
|
+
await deleteBuilder(build.sandboxName);
|
|
3237
|
+
} catch (cleanupError) {
|
|
3238
|
+
logException(
|
|
3239
|
+
cleanupError,
|
|
3240
|
+
"sandbox.workspace_snapshot.builder.delete_failed",
|
|
3241
|
+
{ "app.workspace.id": workspace.id }
|
|
3242
|
+
);
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
async function requireRemainingBuildTime(workspace, build, beforeWrite) {
|
|
3246
|
+
const remainingMs = build.startedAt.getTime() + BUILD_TIMEOUT_MS - Date.now();
|
|
3247
|
+
if (remainingMs > 0) return remainingMs;
|
|
3248
|
+
await markFailed(workspace, build, BUILD_TIMEOUT_ERROR, beforeWrite);
|
|
3249
|
+
throw new Error(BUILD_TIMEOUT_ERROR);
|
|
3250
|
+
}
|
|
3251
|
+
async function finishBuild(workspace, value, signal, beforeWrite) {
|
|
3252
|
+
const { build: loaded } = await loadSnapshotsForProfile(
|
|
3253
|
+
getDb(),
|
|
3254
|
+
workspace.id,
|
|
3255
|
+
value.hash
|
|
3256
|
+
);
|
|
3257
|
+
const build = activeBuild(loaded);
|
|
3258
|
+
if (!build?.commandId || !build.sandboxName) return null;
|
|
3259
|
+
try {
|
|
3260
|
+
await requireRemainingBuildTime(workspace, build, beforeWrite);
|
|
3261
|
+
const sandbox = await getWorkspaceSnapshotBuilder(
|
|
3262
|
+
build.sandboxName,
|
|
3263
|
+
signal
|
|
3264
|
+
);
|
|
3265
|
+
const command = await sandbox.getCommand(build.commandId, { signal });
|
|
3266
|
+
const pollSignal = AbortSignal.timeout(COMMAND_STATUS_POLL_TIMEOUT_MS);
|
|
3267
|
+
const waitSignal = signal ? AbortSignal.any([signal, pollSignal]) : pollSignal;
|
|
3268
|
+
let finished;
|
|
3269
|
+
try {
|
|
3270
|
+
finished = await command.wait({ signal: waitSignal });
|
|
3271
|
+
} catch (error) {
|
|
3272
|
+
if (pollSignal.aborted && !signal?.aborted) return null;
|
|
3273
|
+
throw error;
|
|
3274
|
+
}
|
|
3275
|
+
if (finished.exitCode !== 0) {
|
|
3276
|
+
const error = (await finished.stderr({ signal })).trim() || `exit ${finished.exitCode}`;
|
|
3277
|
+
throw new Error(
|
|
3278
|
+
`Workspace ${workspace.name} snapshot setup failed: ${error}`
|
|
3279
|
+
);
|
|
3280
|
+
}
|
|
3281
|
+
await requireRemainingBuildTime(workspace, build, beforeWrite);
|
|
3282
|
+
const snapshot = await sandbox.snapshot({ signal });
|
|
3283
|
+
await requireRemainingBuildTime(workspace, build, beforeWrite);
|
|
3284
|
+
const createdAtMs = Date.now();
|
|
3285
|
+
const buildDurationMs = Math.max(
|
|
3286
|
+
0,
|
|
3287
|
+
createdAtMs - build.startedAt.getTime()
|
|
3288
|
+
);
|
|
3289
|
+
await beforeWrite();
|
|
3290
|
+
const result = await setWorkspaceSnapshot(
|
|
3291
|
+
workspace.id,
|
|
3292
|
+
{
|
|
3293
|
+
id: snapshot.snapshotId,
|
|
3294
|
+
generatedAt: new Date(createdAtMs),
|
|
3295
|
+
buildDurationMs,
|
|
3296
|
+
profileHash: value.hash
|
|
3297
|
+
},
|
|
3298
|
+
{ buildId: build.id }
|
|
3299
|
+
);
|
|
3300
|
+
requireBuildWrite(result.written, workspace);
|
|
3301
|
+
try {
|
|
3302
|
+
await deleteWorkspaceSnapshotBuilders(result.replacedBuilderNames);
|
|
3303
|
+
} catch (error) {
|
|
3304
|
+
logException(error, "sandbox.workspace_snapshot.builder.delete_failed", {
|
|
3305
|
+
"app.workspace.id": workspace.id,
|
|
3306
|
+
"app.sandbox.snapshot.builder_count": result.replacedBuilderNames.length
|
|
3307
|
+
});
|
|
3308
|
+
}
|
|
3309
|
+
return {
|
|
3310
|
+
snapshotId: snapshot.snapshotId,
|
|
3311
|
+
profileHash: value.hash,
|
|
3312
|
+
dependencyCount: value.dependencyCount,
|
|
3313
|
+
cacheHit: false,
|
|
3314
|
+
resolveOutcome: "rebuilt",
|
|
3315
|
+
rebuildReason: "cache_miss",
|
|
3316
|
+
createdAtMs,
|
|
3317
|
+
buildDurationMs
|
|
3318
|
+
};
|
|
3319
|
+
} catch (error) {
|
|
3320
|
+
if (error instanceof Error && error.message === BUILD_TIMEOUT_ERROR) {
|
|
3321
|
+
throw error;
|
|
3322
|
+
}
|
|
3323
|
+
if (isCancelOrTransient(error)) throw error;
|
|
3324
|
+
await markFailed(workspace, build, error, beforeWrite);
|
|
3325
|
+
throw error;
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
async function startBuild(params) {
|
|
3329
|
+
const { workspace, value, signal } = params;
|
|
3330
|
+
const credentials = getVercelSandboxCredentials();
|
|
3331
|
+
const resources = getSandboxResources();
|
|
3332
|
+
const name = builderName();
|
|
3333
|
+
const buildId = randomUUID3();
|
|
3334
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
3335
|
+
try {
|
|
3336
|
+
await Sandbox.create({
|
|
3337
|
+
name,
|
|
3338
|
+
persistent: true,
|
|
3339
|
+
timeout: BUILD_TIMEOUT_MS,
|
|
3340
|
+
runtime: params.runtime,
|
|
3341
|
+
signal,
|
|
3342
|
+
...credentials ?? {},
|
|
3343
|
+
...resources ? { resources } : {}
|
|
3344
|
+
});
|
|
3345
|
+
await params.beforeWrite();
|
|
3346
|
+
const written = await setWorkspaceSnapshotBuild(
|
|
3347
|
+
workspace.id,
|
|
3348
|
+
{
|
|
3349
|
+
id: buildId,
|
|
3350
|
+
status: "building",
|
|
3351
|
+
phase: "created",
|
|
3352
|
+
profileHash: value.hash,
|
|
3353
|
+
startedAt,
|
|
3354
|
+
sandboxName: name,
|
|
3355
|
+
commandId: null,
|
|
3356
|
+
error: null
|
|
3357
|
+
},
|
|
3358
|
+
{ insertIfMissing: true, expectedRecipe: workspace }
|
|
3359
|
+
);
|
|
3360
|
+
if (!written) {
|
|
3361
|
+
try {
|
|
3362
|
+
await deleteBuilder(name);
|
|
3363
|
+
} catch (cleanupError) {
|
|
3364
|
+
logException(
|
|
3365
|
+
cleanupError,
|
|
3366
|
+
"sandbox.workspace_snapshot.builder.delete_failed",
|
|
3367
|
+
{ "app.workspace.id": workspace.id }
|
|
3368
|
+
);
|
|
3369
|
+
}
|
|
3370
|
+
return;
|
|
3371
|
+
}
|
|
3372
|
+
} catch (error) {
|
|
3373
|
+
try {
|
|
3374
|
+
if (!isCancelOrTransient(error)) {
|
|
3375
|
+
try {
|
|
3376
|
+
await params.beforeWrite();
|
|
3377
|
+
await setWorkspaceSnapshotBuild(
|
|
3378
|
+
workspace.id,
|
|
3379
|
+
{
|
|
3380
|
+
id: buildId,
|
|
3381
|
+
status: "failed",
|
|
3382
|
+
phase: "created",
|
|
3383
|
+
profileHash: value.hash,
|
|
3384
|
+
startedAt,
|
|
3385
|
+
sandboxName: name,
|
|
3386
|
+
commandId: null,
|
|
3387
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3388
|
+
},
|
|
3389
|
+
{ insertIfMissing: true, expectedRecipe: workspace }
|
|
3390
|
+
);
|
|
3391
|
+
} catch {
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
} finally {
|
|
3395
|
+
try {
|
|
3396
|
+
await deleteBuilder(name);
|
|
3397
|
+
} catch (cleanupError) {
|
|
3398
|
+
logException(
|
|
3399
|
+
cleanupError,
|
|
3400
|
+
"sandbox.workspace_snapshot.builder.delete_failed",
|
|
3401
|
+
{ "app.workspace.id": workspace.id }
|
|
3402
|
+
);
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
throw error;
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
async function continueBuild(params) {
|
|
3409
|
+
const { build: loaded } = await loadSnapshotsForProfile(
|
|
3410
|
+
getDb(),
|
|
3411
|
+
params.workspace.id,
|
|
3412
|
+
params.value.hash
|
|
3413
|
+
);
|
|
3414
|
+
const build = activeBuild(loaded);
|
|
3415
|
+
if (!build?.sandboxName || build.commandId) return;
|
|
3416
|
+
const remainingMs = await requireRemainingBuildTime(
|
|
3417
|
+
params.workspace,
|
|
3418
|
+
build,
|
|
3419
|
+
params.beforeWrite
|
|
3420
|
+
);
|
|
3421
|
+
const commandTimeoutMs = Math.min(remainingMs, BUILD_COMMAND_TIMEOUT_MS);
|
|
3422
|
+
const phaseTimeoutSignal = AbortSignal.timeout(commandTimeoutMs);
|
|
3423
|
+
const phaseSignal = params.signal ? AbortSignal.any([params.signal, phaseTimeoutSignal]) : phaseTimeoutSignal;
|
|
3424
|
+
try {
|
|
3425
|
+
const sandbox = await getWorkspaceSnapshotBuilder(
|
|
3426
|
+
build.sandboxName,
|
|
3427
|
+
phaseSignal
|
|
3428
|
+
);
|
|
3429
|
+
const session = createSandboxSession(sandbox);
|
|
3430
|
+
if (build.phase === "created") {
|
|
3431
|
+
await dependencies(
|
|
3432
|
+
session,
|
|
3433
|
+
params.value.dependencies,
|
|
3434
|
+
phaseSignal,
|
|
3435
|
+
commandTimeoutMs
|
|
3436
|
+
);
|
|
3437
|
+
await postinstall(
|
|
3438
|
+
session,
|
|
3439
|
+
params.value.postinstall,
|
|
3440
|
+
phaseSignal,
|
|
3441
|
+
commandTimeoutMs
|
|
3442
|
+
);
|
|
3443
|
+
await requireRemainingBuildTime(
|
|
3444
|
+
params.workspace,
|
|
3445
|
+
build,
|
|
3446
|
+
params.beforeWrite
|
|
3447
|
+
);
|
|
3448
|
+
await params.beforeWrite();
|
|
3449
|
+
const written2 = await setWorkspaceSnapshotBuild(params.workspace.id, {
|
|
3450
|
+
...build,
|
|
3451
|
+
phase: "dependencies_installed"
|
|
3452
|
+
});
|
|
3453
|
+
requireBuildWrite(written2, params.workspace);
|
|
3454
|
+
return;
|
|
3455
|
+
}
|
|
3456
|
+
if (build.phase === "dependencies_installed") {
|
|
3457
|
+
await prepareWorkspaceRepositories({
|
|
3458
|
+
sandbox: session,
|
|
3459
|
+
workspace: params.workspace,
|
|
3460
|
+
signal: phaseSignal,
|
|
3461
|
+
applyNetworkPolicy: params.applyNetworkPolicy,
|
|
3462
|
+
prepareRepositories: params.prepareRepositories,
|
|
3463
|
+
removeCredentialRoute: params.removeCredentialRoute
|
|
3464
|
+
});
|
|
3465
|
+
await requireRemainingBuildTime(
|
|
3466
|
+
params.workspace,
|
|
3467
|
+
build,
|
|
3468
|
+
params.beforeWrite
|
|
3469
|
+
);
|
|
3470
|
+
await params.beforeWrite();
|
|
3471
|
+
const written2 = await setWorkspaceSnapshotBuild(params.workspace.id, {
|
|
3472
|
+
...build,
|
|
3473
|
+
phase: "repositories_prepared"
|
|
3474
|
+
});
|
|
3475
|
+
requireBuildWrite(written2, params.workspace);
|
|
3476
|
+
return;
|
|
3477
|
+
}
|
|
3478
|
+
await params.beforeWrite();
|
|
3479
|
+
const command = await sandbox.runCommand({
|
|
3480
|
+
...workspaceSnapshotSetupCommand(params.workspace, build.id),
|
|
3481
|
+
detached: true,
|
|
3482
|
+
timeoutMs: remainingMs,
|
|
3483
|
+
signal: phaseSignal
|
|
3484
|
+
});
|
|
3485
|
+
await params.beforeWrite();
|
|
3486
|
+
const written = await setWorkspaceSnapshotBuild(params.workspace.id, {
|
|
3487
|
+
...build,
|
|
3488
|
+
commandId: command.cmdId,
|
|
3489
|
+
error: null
|
|
3490
|
+
});
|
|
3491
|
+
requireBuildWrite(written, params.workspace);
|
|
3492
|
+
} catch (error) {
|
|
3493
|
+
if (phaseTimeoutSignal.aborted && !params.signal?.aborted) {
|
|
3494
|
+
const timeoutError = new Error(BUILD_COMMAND_TIMEOUT_ERROR, {
|
|
3495
|
+
cause: error
|
|
3496
|
+
});
|
|
3497
|
+
await markFailed(
|
|
3498
|
+
params.workspace,
|
|
3499
|
+
build,
|
|
3500
|
+
timeoutError,
|
|
3501
|
+
params.beforeWrite
|
|
3502
|
+
);
|
|
3503
|
+
throw timeoutError;
|
|
3504
|
+
}
|
|
3505
|
+
if (error instanceof Error && error.message === BUILD_TIMEOUT_ERROR) {
|
|
3506
|
+
throw error;
|
|
3507
|
+
}
|
|
3508
|
+
if (isCancelOrTransient(error)) throw error;
|
|
3509
|
+
await markFailed(params.workspace, build, error, params.beforeWrite);
|
|
3510
|
+
throw error;
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
async function advanceWorkspaceSnapshot(params) {
|
|
3514
|
+
const value = create(params.runtime, params.workspace);
|
|
3515
|
+
if (!value) {
|
|
3516
|
+
throw new Error(
|
|
3517
|
+
`Workspace ${params.workspace.name} has no snapshot profile`
|
|
3518
|
+
);
|
|
3519
|
+
}
|
|
3520
|
+
const sqlCached = await snapshotFromSql(
|
|
3521
|
+
params.workspace.id,
|
|
3522
|
+
value,
|
|
3523
|
+
params.staleSnapshotId
|
|
3524
|
+
);
|
|
3525
|
+
if (sqlCached) return sqlCached;
|
|
3526
|
+
const state = getStateAdapter();
|
|
3527
|
+
await state.connect();
|
|
3528
|
+
const locked = await withLock(
|
|
3529
|
+
state,
|
|
3530
|
+
buildLockKey(value.hash),
|
|
3531
|
+
async (lock) => {
|
|
3532
|
+
const workspace = await getWorkspace(getDb(), params.workspace.id);
|
|
3533
|
+
if (!workspace) throw workspaceDeletedError(params.workspace);
|
|
3534
|
+
const lockedValue = create(params.runtime, workspace) ?? value;
|
|
3535
|
+
if (lockedValue.hash !== value.hash) return null;
|
|
3536
|
+
const beforeWrite = async () => {
|
|
3537
|
+
await fenceLock(state, lock, BUILD_LOCK_TTL_MS);
|
|
3538
|
+
};
|
|
3539
|
+
const sqlInsideLock = await snapshotFromSql(
|
|
3540
|
+
workspace.id,
|
|
3541
|
+
lockedValue,
|
|
3542
|
+
params.staleSnapshotId
|
|
3543
|
+
);
|
|
3544
|
+
if (sqlInsideLock) return sqlInsideLock;
|
|
3545
|
+
const finished = await finishBuild(
|
|
3546
|
+
workspace,
|
|
3547
|
+
lockedValue,
|
|
3548
|
+
params.signal,
|
|
3549
|
+
beforeWrite
|
|
3550
|
+
);
|
|
3551
|
+
if (finished) return finished;
|
|
3552
|
+
const { build: loaded } = await loadSnapshotsForProfile(
|
|
3553
|
+
getDb(),
|
|
3554
|
+
workspace.id,
|
|
3555
|
+
lockedValue.hash
|
|
3556
|
+
);
|
|
3557
|
+
const build = activeBuild(loaded);
|
|
3558
|
+
if (build && !build.commandId) {
|
|
3559
|
+
await continueBuild({
|
|
3560
|
+
...params,
|
|
3561
|
+
workspace,
|
|
3562
|
+
value: lockedValue,
|
|
3563
|
+
beforeWrite
|
|
3564
|
+
});
|
|
3565
|
+
} else if (!build) {
|
|
3566
|
+
if (loaded?.sandboxName) {
|
|
3567
|
+
try {
|
|
3568
|
+
await deleteBuilder(loaded.sandboxName);
|
|
3569
|
+
} catch (cleanupError) {
|
|
3570
|
+
logException(
|
|
3571
|
+
cleanupError,
|
|
3572
|
+
"sandbox.workspace_snapshot.builder.delete_failed",
|
|
3573
|
+
{ "app.workspace.id": workspace.id }
|
|
3574
|
+
);
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
await startBuild({
|
|
3578
|
+
workspace,
|
|
3579
|
+
value: lockedValue,
|
|
3580
|
+
runtime: params.runtime,
|
|
3581
|
+
signal: params.signal,
|
|
3582
|
+
beforeWrite
|
|
3583
|
+
});
|
|
3584
|
+
}
|
|
3585
|
+
return null;
|
|
3586
|
+
},
|
|
3587
|
+
{ ttlMs: BUILD_LOCK_TTL_MS, keepAlive: true }
|
|
3588
|
+
);
|
|
3589
|
+
return locked.acquired ? locked.value ?? null : null;
|
|
3590
|
+
}
|
|
3591
|
+
function shouldYieldWait(shouldYield) {
|
|
3592
|
+
if (shouldYield?.()) return true;
|
|
3593
|
+
const deadlineAtMs = getTurnRequestDeadline()?.deadlineAtMs;
|
|
3594
|
+
return deadlineAtMs !== void 0 && Date.now() >= deadlineAtMs - WAIT_YIELD_BUFFER_MS;
|
|
3595
|
+
}
|
|
3596
|
+
async function resolveWorkspaceSnapshot(params) {
|
|
3597
|
+
for (; ; ) {
|
|
3598
|
+
params.signal?.throwIfAborted();
|
|
3599
|
+
const workspace = await getWorkspace(getDb(), params.workspace.id);
|
|
3600
|
+
if (!workspace) throw workspaceDeletedError(params.workspace);
|
|
3601
|
+
const slice = { ...params, workspace };
|
|
3602
|
+
if (shouldYieldWait(slice.shouldYield)) {
|
|
3603
|
+
throw new WorkspaceSnapshotWaitingError(workspace.name);
|
|
3604
|
+
}
|
|
3605
|
+
let snapshot;
|
|
3606
|
+
try {
|
|
3607
|
+
snapshot = await advanceWorkspaceSnapshot(slice);
|
|
3608
|
+
} catch (error) {
|
|
3609
|
+
if (!isSandboxApiTransientError(error)) throw error;
|
|
3610
|
+
if (shouldYieldWait(slice.shouldYield)) {
|
|
3611
|
+
throw new WorkspaceSnapshotWaitingError(workspace.name);
|
|
3612
|
+
}
|
|
3613
|
+
await sleep(TRANSIENT_API_RETRY_MS, params.signal);
|
|
3614
|
+
continue;
|
|
3615
|
+
}
|
|
3616
|
+
if (shouldYieldWait(slice.shouldYield)) {
|
|
3617
|
+
throw new WorkspaceSnapshotWaitingError(workspace.name);
|
|
3618
|
+
}
|
|
3619
|
+
if (snapshot) return snapshot;
|
|
3620
|
+
await sleep(WAIT_POLL_MS, params.signal);
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
|
|
2993
3624
|
// src/chat/sandbox/skill-sync.ts
|
|
2994
3625
|
import fs3 from "fs/promises";
|
|
2995
3626
|
import path3 from "path";
|
|
@@ -3247,7 +3878,7 @@ function createSandboxRuntime(options) {
|
|
|
3247
3878
|
const adaptSandbox = (vercelSandbox) => createSandboxSession(vercelSandbox, {
|
|
3248
3879
|
onUnavailable: invalidateSession
|
|
3249
3880
|
});
|
|
3250
|
-
const createSandboxName = () => `${SANDBOX_NAME_PREFIX}${
|
|
3881
|
+
const createSandboxName = () => `${SANDBOX_NAME_PREFIX}${randomUUID4()}`;
|
|
3251
3882
|
const preflightNetworkPolicy = (sandboxName) => options.createNetworkPolicy?.(sandboxName);
|
|
3252
3883
|
const persistSandboxRef = async (nextRef) => {
|
|
3253
3884
|
if (reportedSandboxRef?.id === nextRef.id && reportedSandboxRef.profileHash === nextRef.profileHash && reportedSandboxRef.workspaceId === nextRef.workspaceId) {
|
|
@@ -3339,7 +3970,7 @@ function createSandboxRuntime(options) {
|
|
|
3339
3970
|
const networkPolicy = preflightNetworkPolicy(sandboxName);
|
|
3340
3971
|
try {
|
|
3341
3972
|
return adaptSandbox(
|
|
3342
|
-
await
|
|
3973
|
+
await Sandbox2.create({
|
|
3343
3974
|
timeout: timeoutMs,
|
|
3344
3975
|
...networkPolicy ? { name: sandboxName, persistent: false, networkPolicy } : {},
|
|
3345
3976
|
source: {
|
|
@@ -3360,20 +3991,6 @@ function createSandboxRuntime(options) {
|
|
|
3360
3991
|
}
|
|
3361
3992
|
throw new Error(`Failed to boot sandbox from snapshot ${snapshotId}`);
|
|
3362
3993
|
};
|
|
3363
|
-
const setSnapshotAttributes = (snapshot) => {
|
|
3364
|
-
setSpanAttributes({
|
|
3365
|
-
"app.sandbox.source": snapshot.snapshotId ? "snapshot" : "created",
|
|
3366
|
-
"app.sandbox.snapshot.cache_hit": snapshot.cacheHit,
|
|
3367
|
-
"app.sandbox.snapshot.resolve_outcome": snapshot.resolveOutcome,
|
|
3368
|
-
...snapshot.profileHash ? {
|
|
3369
|
-
"app.sandbox.snapshot.profile_hash": snapshot.profileHash
|
|
3370
|
-
} : {},
|
|
3371
|
-
"app.sandbox.snapshot.dependency_count": snapshot.dependencyCount,
|
|
3372
|
-
...snapshot.rebuildReason ? {
|
|
3373
|
-
"app.sandbox.snapshot.rebuild_reason": snapshot.rebuildReason
|
|
3374
|
-
} : {}
|
|
3375
|
-
});
|
|
3376
|
-
};
|
|
3377
3994
|
const createSandboxFromResolvedSnapshot = async (params) => {
|
|
3378
3995
|
const { runtime, snapshot, sandboxCredentials, sandboxName, signal } = params;
|
|
3379
3996
|
signal?.throwIfAborted();
|
|
@@ -3381,7 +3998,7 @@ function createSandboxRuntime(options) {
|
|
|
3381
3998
|
const networkPolicy = preflightNetworkPolicy(sandboxName);
|
|
3382
3999
|
const resources = getSandboxResources();
|
|
3383
4000
|
const session = adaptSandbox(
|
|
3384
|
-
await
|
|
4001
|
+
await Sandbox2.create({
|
|
3385
4002
|
timeout: timeoutMs,
|
|
3386
4003
|
runtime,
|
|
3387
4004
|
...networkPolicy ? { name: sandboxName, persistent: false, networkPolicy } : {},
|
|
@@ -3403,13 +4020,21 @@ function createSandboxRuntime(options) {
|
|
|
3403
4020
|
} catch (error) {
|
|
3404
4021
|
if (!isMissingError(error)) throw error;
|
|
3405
4022
|
setSpanAttributes({ "app.sandbox.snapshot.rebuild_after_missing": true });
|
|
3406
|
-
const rebuilt = await
|
|
4023
|
+
const rebuilt = params.workspace ? await resolveWorkspaceSnapshot({
|
|
4024
|
+
workspace: params.workspace,
|
|
4025
|
+
runtime,
|
|
4026
|
+
staleSnapshotId: snapshot.snapshotId,
|
|
4027
|
+
signal,
|
|
4028
|
+
shouldYield: options.shouldYield,
|
|
4029
|
+
applyNetworkPolicy,
|
|
4030
|
+
prepareRepositories: options.onWorkspacePrepare,
|
|
4031
|
+
removeCredentialRoute: Boolean(options.createNetworkPolicy)
|
|
4032
|
+
}) : await resolve({
|
|
3407
4033
|
runtime,
|
|
3408
4034
|
timeoutMs,
|
|
3409
4035
|
forceRebuild: true,
|
|
3410
4036
|
staleSnapshotId: snapshot.snapshotId,
|
|
3411
4037
|
signal,
|
|
3412
|
-
workspace: params.workspace,
|
|
3413
4038
|
prepareWorkspace: params.prepareWorkspace
|
|
3414
4039
|
});
|
|
3415
4040
|
if (!rebuilt.snapshotId) throw error;
|
|
@@ -3446,15 +4071,22 @@ function createSandboxRuntime(options) {
|
|
|
3446
4071
|
"app.sandbox.runtime": runtime
|
|
3447
4072
|
},
|
|
3448
4073
|
async () => {
|
|
3449
|
-
const snapshot = await
|
|
4074
|
+
const snapshot = workspace ? await resolveWorkspaceSnapshot({
|
|
4075
|
+
workspace,
|
|
4076
|
+
runtime,
|
|
4077
|
+
signal,
|
|
4078
|
+
shouldYield: options.shouldYield,
|
|
4079
|
+
applyNetworkPolicy,
|
|
4080
|
+
prepareRepositories: options.onWorkspacePrepare,
|
|
4081
|
+
removeCredentialRoute: Boolean(options.createNetworkPolicy)
|
|
4082
|
+
}) : await resolve({
|
|
3450
4083
|
runtime,
|
|
3451
4084
|
timeoutMs,
|
|
3452
4085
|
signal,
|
|
3453
|
-
workspace,
|
|
3454
4086
|
prepareWorkspace
|
|
3455
4087
|
});
|
|
3456
4088
|
signal?.throwIfAborted();
|
|
3457
|
-
|
|
4089
|
+
setSnapshotSpanAttributes(snapshot);
|
|
3458
4090
|
const created = await createSandboxFromResolvedSnapshot({
|
|
3459
4091
|
runtime,
|
|
3460
4092
|
snapshot,
|
|
@@ -3464,12 +4096,6 @@ function createSandboxRuntime(options) {
|
|
|
3464
4096
|
workspace,
|
|
3465
4097
|
prepareWorkspace
|
|
3466
4098
|
});
|
|
3467
|
-
if (workspace) {
|
|
3468
|
-
await recordResolvedWorkspaceSnapshot(
|
|
3469
|
-
workspace.id,
|
|
3470
|
-
created.snapshot
|
|
3471
|
-
);
|
|
3472
|
-
}
|
|
3473
4099
|
return created.session;
|
|
3474
4100
|
}
|
|
3475
4101
|
);
|
|
@@ -3488,6 +4114,9 @@ function createSandboxRuntime(options) {
|
|
|
3488
4114
|
let networkPolicyKey;
|
|
3489
4115
|
try {
|
|
3490
4116
|
networkPolicyKey = await applyNetworkPolicy(createdSandbox);
|
|
4117
|
+
if (workspace) {
|
|
4118
|
+
await options.onWorkspacePrepare?.(createdSandbox, workspace, signal);
|
|
4119
|
+
}
|
|
3491
4120
|
await prepareSandbox(createdSandbox);
|
|
3492
4121
|
} catch (error) {
|
|
3493
4122
|
if (workspace) await stopSession(createdSandbox);
|
|
@@ -3551,7 +4180,7 @@ function createSandboxRuntime(options) {
|
|
|
3551
4180
|
"app.sandbox.source": "id_hint"
|
|
3552
4181
|
},
|
|
3553
4182
|
async () => adaptSandbox(
|
|
3554
|
-
await
|
|
4183
|
+
await Sandbox2.get({
|
|
3555
4184
|
name: ref.id,
|
|
3556
4185
|
resume: true,
|
|
3557
4186
|
...sandboxCredentials ?? {},
|
|
@@ -4636,6 +5265,11 @@ async function grepFilesWithRipgrep(params) {
|
|
|
4636
5265
|
});
|
|
4637
5266
|
if (result.exitCode !== 0 && result.exitCode !== 1) {
|
|
4638
5267
|
const detail = result.stderr.trim() || result.stdout.trim() || "command failed";
|
|
5268
|
+
if (/error parsing glob/i.test(detail)) {
|
|
5269
|
+
throw new ToolInputError(`Invalid glob: ${params.glob ?? ""}`, {
|
|
5270
|
+
cause: new Error(detail)
|
|
5271
|
+
});
|
|
5272
|
+
}
|
|
4639
5273
|
if (/regex parse error|error parsing regex/i.test(detail)) {
|
|
4640
5274
|
throw new ToolInputError(`Invalid regex pattern: ${params.pattern}`, {
|
|
4641
5275
|
cause: new Error(detail)
|
|
@@ -5090,6 +5724,7 @@ function createSandbox(options) {
|
|
|
5090
5724
|
skills: options.skills,
|
|
5091
5725
|
referenceFiles: options.referenceFiles,
|
|
5092
5726
|
timeoutMs: options.timeoutMs,
|
|
5727
|
+
shouldYield: options.shouldYield,
|
|
5093
5728
|
traceContext,
|
|
5094
5729
|
commandEnv: credentialEgress ? resolveSandboxCommandEnvironment : void 0,
|
|
5095
5730
|
createNetworkPolicy: credentialEgress || hasTracePropagationDomains ? (sessionId, traceHeaders) => buildSandboxEgressNetworkPolicy({
|
|
@@ -5543,9 +6178,9 @@ function createSandbox(options) {
|
|
|
5543
6178
|
return void 0;
|
|
5544
6179
|
}
|
|
5545
6180
|
const { fs: fs5 } = await runtime.tools();
|
|
5546
|
-
const workspaceDirectories = activeWorkspace?.repos.map((repo) => tryWorkspaceRepoCheckoutPath(repo.repo)).filter(
|
|
5547
|
-
(checkoutPath) =>
|
|
5548
|
-
) ?? [];
|
|
6181
|
+
const workspaceDirectories = activeWorkspace?.repos.map((repo) => tryWorkspaceRepoCheckoutPath(repo.repo)).filter(
|
|
6182
|
+
(checkoutPath) => Boolean(checkoutPath)
|
|
6183
|
+
).map((checkoutPath) => `${SANDBOX_WORKSPACE_ROOT}/${checkoutPath}`) ?? [];
|
|
5549
6184
|
const directories = workspaceDirectories.length > 0 ? workspaceDirectories : await listRepositoryDirectories(fs5);
|
|
5550
6185
|
return await resolveRepositoryInstructionsForDirectories({
|
|
5551
6186
|
directories,
|
|
@@ -5985,6 +6620,7 @@ function createAgentSandbox(options) {
|
|
|
5985
6620
|
tracePropagation: options.tracePropagation,
|
|
5986
6621
|
egressSignals: options.egressSignals,
|
|
5987
6622
|
credentialEgress: options.credentialEgress,
|
|
6623
|
+
shouldYield: options.shouldYield,
|
|
5988
6624
|
prepare: options.prepareSandbox,
|
|
5989
6625
|
prepareWorkspace: options.prepareWorkspace,
|
|
5990
6626
|
onSandboxRefChanged: async (sandboxRef) => {
|
|
@@ -8173,6 +8809,296 @@ import { z as z28 } from "zod";
|
|
|
8173
8809
|
|
|
8174
8810
|
// src/chat/scheduled-tasks/schedule-intent.ts
|
|
8175
8811
|
import { z as z26 } from "zod";
|
|
8812
|
+
|
|
8813
|
+
// src/chat/scheduled-tasks/cadence.ts
|
|
8814
|
+
var FORMATTERS = /* @__PURE__ */ new Map();
|
|
8815
|
+
function getFormatter(timezone) {
|
|
8816
|
+
const existing = FORMATTERS.get(timezone);
|
|
8817
|
+
if (existing) {
|
|
8818
|
+
return existing;
|
|
8819
|
+
}
|
|
8820
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
8821
|
+
timeZone: timezone,
|
|
8822
|
+
hour12: false,
|
|
8823
|
+
year: "numeric",
|
|
8824
|
+
month: "2-digit",
|
|
8825
|
+
day: "2-digit",
|
|
8826
|
+
hour: "2-digit",
|
|
8827
|
+
minute: "2-digit",
|
|
8828
|
+
second: "2-digit"
|
|
8829
|
+
});
|
|
8830
|
+
FORMATTERS.set(timezone, formatter);
|
|
8831
|
+
return formatter;
|
|
8832
|
+
}
|
|
8833
|
+
function normalizeHour(hour) {
|
|
8834
|
+
return hour === 24 ? 0 : hour;
|
|
8835
|
+
}
|
|
8836
|
+
function getLocalDateWeekday(date) {
|
|
8837
|
+
return new Date(Date.UTC(date.year, date.month - 1, date.day)).getUTCDay();
|
|
8838
|
+
}
|
|
8839
|
+
function getWeekStart(date) {
|
|
8840
|
+
return addDays(date, -((getLocalDateWeekday(date) + 6) % 7));
|
|
8841
|
+
}
|
|
8842
|
+
function getZonedDateTimeParts(timestampMs, timezone) {
|
|
8843
|
+
const parts = getFormatter(timezone).formatToParts(new Date(timestampMs));
|
|
8844
|
+
const values = new Map(parts.map((part) => [part.type, part.value]));
|
|
8845
|
+
const year = Number(values.get("year"));
|
|
8846
|
+
const month = Number(values.get("month"));
|
|
8847
|
+
const day = Number(values.get("day"));
|
|
8848
|
+
const hour = normalizeHour(Number(values.get("hour")));
|
|
8849
|
+
const minute = Number(values.get("minute"));
|
|
8850
|
+
const second = Number(values.get("second"));
|
|
8851
|
+
return {
|
|
8852
|
+
year,
|
|
8853
|
+
month,
|
|
8854
|
+
day,
|
|
8855
|
+
hour,
|
|
8856
|
+
minute,
|
|
8857
|
+
second,
|
|
8858
|
+
weekday: getLocalDateWeekday({ year, month, day })
|
|
8859
|
+
};
|
|
8860
|
+
}
|
|
8861
|
+
function getTimeZoneOffsetMs(timestampMs, timezone) {
|
|
8862
|
+
const parts = getZonedDateTimeParts(timestampMs, timezone);
|
|
8863
|
+
return Date.UTC(
|
|
8864
|
+
parts.year,
|
|
8865
|
+
parts.month - 1,
|
|
8866
|
+
parts.day,
|
|
8867
|
+
parts.hour,
|
|
8868
|
+
parts.minute,
|
|
8869
|
+
parts.second
|
|
8870
|
+
) - timestampMs;
|
|
8871
|
+
}
|
|
8872
|
+
function localDateTimeToTimestampMs(args) {
|
|
8873
|
+
const localAsUtcMs = Date.UTC(
|
|
8874
|
+
args.date.year,
|
|
8875
|
+
args.date.month - 1,
|
|
8876
|
+
args.date.day,
|
|
8877
|
+
args.time.hour,
|
|
8878
|
+
args.time.minute,
|
|
8879
|
+
0
|
|
8880
|
+
);
|
|
8881
|
+
const offsets = /* @__PURE__ */ new Set();
|
|
8882
|
+
for (const probeDeltaMs of [
|
|
8883
|
+
-36 * 60 * 60 * 1e3,
|
|
8884
|
+
-12 * 60 * 60 * 1e3,
|
|
8885
|
+
0,
|
|
8886
|
+
12 * 60 * 60 * 1e3,
|
|
8887
|
+
36 * 60 * 60 * 1e3
|
|
8888
|
+
]) {
|
|
8889
|
+
offsets.add(
|
|
8890
|
+
getTimeZoneOffsetMs(localAsUtcMs + probeDeltaMs, args.timezone)
|
|
8891
|
+
);
|
|
8892
|
+
}
|
|
8893
|
+
const matches = [...offsets].map((offsetMs) => localAsUtcMs - offsetMs).filter((timestampMs) => {
|
|
8894
|
+
const parts = getZonedDateTimeParts(timestampMs, args.timezone);
|
|
8895
|
+
return parts.year === args.date.year && parts.month === args.date.month && parts.day === args.date.day && parts.hour === args.time.hour && parts.minute === args.time.minute;
|
|
8896
|
+
});
|
|
8897
|
+
if (matches.length === 0) {
|
|
8898
|
+
return void 0;
|
|
8899
|
+
}
|
|
8900
|
+
return Math.min(...matches);
|
|
8901
|
+
}
|
|
8902
|
+
function resolveLocalScheduleAtMs(args) {
|
|
8903
|
+
const date = parseLocalDate(args.date);
|
|
8904
|
+
if (!date) {
|
|
8905
|
+
return void 0;
|
|
8906
|
+
}
|
|
8907
|
+
return localDateTimeToTimestampMs({
|
|
8908
|
+
date,
|
|
8909
|
+
time: args.time,
|
|
8910
|
+
timezone: args.timezone
|
|
8911
|
+
});
|
|
8912
|
+
}
|
|
8913
|
+
function daysBetween(left, right) {
|
|
8914
|
+
return Math.floor(
|
|
8915
|
+
(Date.UTC(right.year, right.month - 1, right.day) - Date.UTC(left.year, left.month - 1, left.day)) / (24 * 60 * 60 * 1e3)
|
|
8916
|
+
);
|
|
8917
|
+
}
|
|
8918
|
+
function weeklyRecurrenceMatchesDate(date, start, recurrence) {
|
|
8919
|
+
if (compareDate(date, start) < 0) {
|
|
8920
|
+
return false;
|
|
8921
|
+
}
|
|
8922
|
+
return normalizeWeekdays(recurrence.weekdays).includes(
|
|
8923
|
+
getLocalDateWeekday(date)
|
|
8924
|
+
) && Math.floor(daysBetween(getWeekStart(start), getWeekStart(date)) / 7) % recurrence.interval === 0;
|
|
8925
|
+
}
|
|
8926
|
+
function greatestCommonDivisor(left, right) {
|
|
8927
|
+
let a = Math.abs(left);
|
|
8928
|
+
let b = Math.abs(right);
|
|
8929
|
+
while (b !== 0) {
|
|
8930
|
+
[a, b] = [b, a % b];
|
|
8931
|
+
}
|
|
8932
|
+
return a;
|
|
8933
|
+
}
|
|
8934
|
+
function findNextRunAtMs(args) {
|
|
8935
|
+
const start = parseLocalDate(args.recurrence.startDate);
|
|
8936
|
+
const interval = args.recurrence.interval;
|
|
8937
|
+
if (!start || !Number.isInteger(interval) || interval <= 0) {
|
|
8938
|
+
return void 0;
|
|
8939
|
+
}
|
|
8940
|
+
const searchFrom = compareDate(args.searchFrom, start) < 0 ? start : args.searchFrom;
|
|
8941
|
+
if (args.recurrence.frequency === "daily") {
|
|
8942
|
+
const offsetDays = daysBetween(start, searchFrom);
|
|
8943
|
+
let candidateDate = addDays(
|
|
8944
|
+
start,
|
|
8945
|
+
Math.ceil(offsetDays / interval) * interval
|
|
8946
|
+
);
|
|
8947
|
+
const gregorianCycleDays = 146097;
|
|
8948
|
+
const candidateCount2 = gregorianCycleDays / greatestCommonDivisor(gregorianCycleDays, interval);
|
|
8949
|
+
for (let attempts = 0; attempts < candidateCount2; attempts += 1) {
|
|
8950
|
+
const candidate = buildCandidate({
|
|
8951
|
+
date: candidateDate,
|
|
8952
|
+
recurrence: args.recurrence,
|
|
8953
|
+
timezone: args.timezone
|
|
8954
|
+
});
|
|
8955
|
+
if (candidate !== void 0 && candidate > args.afterMs) {
|
|
8956
|
+
return candidate;
|
|
8957
|
+
}
|
|
8958
|
+
candidateDate = addDays(candidateDate, interval);
|
|
8959
|
+
}
|
|
8960
|
+
return void 0;
|
|
8961
|
+
}
|
|
8962
|
+
if (args.recurrence.frequency === "weekly") {
|
|
8963
|
+
if (normalizeWeekdays(args.recurrence.weekdays).length === 0) {
|
|
8964
|
+
return void 0;
|
|
8965
|
+
}
|
|
8966
|
+
let candidateDate = searchFrom;
|
|
8967
|
+
const gregorianCycleDays = 146097;
|
|
8968
|
+
const cadenceDays = interval * 7;
|
|
8969
|
+
const searchDays = gregorianCycleDays * cadenceDays / greatestCommonDivisor(gregorianCycleDays, cadenceDays);
|
|
8970
|
+
for (let attempts = 0; attempts < searchDays; attempts += 1) {
|
|
8971
|
+
if (weeklyRecurrenceMatchesDate(candidateDate, start, args.recurrence)) {
|
|
8972
|
+
const candidate = buildCandidate({
|
|
8973
|
+
date: candidateDate,
|
|
8974
|
+
recurrence: args.recurrence,
|
|
8975
|
+
timezone: args.timezone
|
|
8976
|
+
});
|
|
8977
|
+
if (candidate !== void 0 && candidate > args.afterMs) {
|
|
8978
|
+
return candidate;
|
|
8979
|
+
}
|
|
8980
|
+
}
|
|
8981
|
+
candidateDate = addDays(candidateDate, 1);
|
|
8982
|
+
}
|
|
8983
|
+
return void 0;
|
|
8984
|
+
}
|
|
8985
|
+
if (args.recurrence.frequency === "monthly") {
|
|
8986
|
+
const startMonth = start.year * 12 + start.month - 1;
|
|
8987
|
+
const searchMonth = searchFrom.year * 12 + searchFrom.month - 1;
|
|
8988
|
+
let candidateMonth = startMonth + Math.ceil((searchMonth - startMonth) / interval) * interval;
|
|
8989
|
+
const gregorianCycleMonths = 4800;
|
|
8990
|
+
const candidateCount2 = gregorianCycleMonths / greatestCommonDivisor(gregorianCycleMonths, interval);
|
|
8991
|
+
for (let attempts = 0; attempts < candidateCount2; attempts += 1) {
|
|
8992
|
+
const candidateDate = {
|
|
8993
|
+
year: Math.floor(candidateMonth / 12),
|
|
8994
|
+
month: candidateMonth % 12 + 1,
|
|
8995
|
+
day: args.recurrence.dayOfMonth ?? 0
|
|
8996
|
+
};
|
|
8997
|
+
if (compareDate(candidateDate, searchFrom) >= 0) {
|
|
8998
|
+
const candidate = buildCandidate({
|
|
8999
|
+
date: candidateDate,
|
|
9000
|
+
recurrence: args.recurrence,
|
|
9001
|
+
timezone: args.timezone
|
|
9002
|
+
});
|
|
9003
|
+
if (candidate !== void 0 && candidate > args.afterMs) {
|
|
9004
|
+
return candidate;
|
|
9005
|
+
}
|
|
9006
|
+
}
|
|
9007
|
+
candidateMonth += interval;
|
|
9008
|
+
}
|
|
9009
|
+
return void 0;
|
|
9010
|
+
}
|
|
9011
|
+
let candidateYear = start.year + Math.ceil((searchFrom.year - start.year) / interval) * interval;
|
|
9012
|
+
const candidateCount = 400 / greatestCommonDivisor(400, interval);
|
|
9013
|
+
for (let attempts = 0; attempts < candidateCount; attempts += 1) {
|
|
9014
|
+
const candidateDate = {
|
|
9015
|
+
year: candidateYear,
|
|
9016
|
+
month: args.recurrence.month ?? 0,
|
|
9017
|
+
day: args.recurrence.dayOfMonth ?? 0
|
|
9018
|
+
};
|
|
9019
|
+
if (compareDate(candidateDate, searchFrom) >= 0) {
|
|
9020
|
+
const candidate = buildCandidate({
|
|
9021
|
+
date: candidateDate,
|
|
9022
|
+
recurrence: args.recurrence,
|
|
9023
|
+
timezone: args.timezone
|
|
9024
|
+
});
|
|
9025
|
+
if (candidate !== void 0 && candidate > args.afterMs) {
|
|
9026
|
+
return candidate;
|
|
9027
|
+
}
|
|
9028
|
+
}
|
|
9029
|
+
candidateYear += interval;
|
|
9030
|
+
}
|
|
9031
|
+
return void 0;
|
|
9032
|
+
}
|
|
9033
|
+
function getFirstRunAtMs(args) {
|
|
9034
|
+
return findNextRunAtMs({
|
|
9035
|
+
...args,
|
|
9036
|
+
searchFrom: getLocalDate(args.afterMs, args.timezone)
|
|
9037
|
+
});
|
|
9038
|
+
}
|
|
9039
|
+
function compareDate(left, right) {
|
|
9040
|
+
return Date.UTC(left.year, left.month - 1, left.day) - Date.UTC(right.year, right.month - 1, right.day);
|
|
9041
|
+
}
|
|
9042
|
+
function addDays(date, days) {
|
|
9043
|
+
const next = new Date(Date.UTC(date.year, date.month - 1, date.day + days));
|
|
9044
|
+
return {
|
|
9045
|
+
year: next.getUTCFullYear(),
|
|
9046
|
+
month: next.getUTCMonth() + 1,
|
|
9047
|
+
day: next.getUTCDate()
|
|
9048
|
+
};
|
|
9049
|
+
}
|
|
9050
|
+
function daysInMonth(year, month) {
|
|
9051
|
+
return new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
9052
|
+
}
|
|
9053
|
+
function parseLocalDate(value) {
|
|
9054
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
9055
|
+
if (!match) {
|
|
9056
|
+
return void 0;
|
|
9057
|
+
}
|
|
9058
|
+
const year = Number(match[1]);
|
|
9059
|
+
const month = Number(match[2]);
|
|
9060
|
+
const day = Number(match[3]);
|
|
9061
|
+
if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day) || month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month)) {
|
|
9062
|
+
return void 0;
|
|
9063
|
+
}
|
|
9064
|
+
return { year, month, day };
|
|
9065
|
+
}
|
|
9066
|
+
function getLocalDate(timestampMs, timezone) {
|
|
9067
|
+
const parts = getZonedDateTimeParts(timestampMs, timezone);
|
|
9068
|
+
return { year: parts.year, month: parts.month, day: parts.day };
|
|
9069
|
+
}
|
|
9070
|
+
function normalizeWeekdays(values) {
|
|
9071
|
+
return [
|
|
9072
|
+
...new Set((values ?? []).filter((value) => value >= 0 && value <= 6))
|
|
9073
|
+
].sort((a, b) => a - b);
|
|
9074
|
+
}
|
|
9075
|
+
function buildCandidate(args) {
|
|
9076
|
+
return localDateTimeToTimestampMs({
|
|
9077
|
+
date: args.date,
|
|
9078
|
+
time: args.recurrence.time,
|
|
9079
|
+
timezone: args.timezone
|
|
9080
|
+
});
|
|
9081
|
+
}
|
|
9082
|
+
function getNextRunAtMs(task, scheduledForMs, afterMs = scheduledForMs) {
|
|
9083
|
+
if (task.schedule.kind !== "recurring") {
|
|
9084
|
+
return void 0;
|
|
9085
|
+
}
|
|
9086
|
+
const recurrence = task.schedule.recurrence;
|
|
9087
|
+
if (!recurrence || !Number.isFinite(recurrence.interval) || recurrence.interval <= 0) {
|
|
9088
|
+
return void 0;
|
|
9089
|
+
}
|
|
9090
|
+
const timezone = task.schedule.timezone;
|
|
9091
|
+
const afterDate = getLocalDate(afterMs, timezone);
|
|
9092
|
+
const nextScheduledDate = addDays(getLocalDate(scheduledForMs, timezone), 1);
|
|
9093
|
+
return findNextRunAtMs({
|
|
9094
|
+
afterMs,
|
|
9095
|
+
recurrence,
|
|
9096
|
+
searchFrom: compareDate(afterDate, nextScheduledDate) < 0 ? nextScheduledDate : afterDate,
|
|
9097
|
+
timezone
|
|
9098
|
+
});
|
|
9099
|
+
}
|
|
9100
|
+
|
|
9101
|
+
// src/chat/scheduled-tasks/schedule-intent.ts
|
|
8176
9102
|
var localDateSchema = z26.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Use a local date in YYYY-MM-DD format.");
|
|
8177
9103
|
var localTimeSchema = z26.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "Use local time in HH:MM format.");
|
|
8178
9104
|
var timezoneSchema = z26.string().min(1).max(80).describe(
|
|
@@ -8420,6 +9346,41 @@ function compileScheduleIntent(args) {
|
|
|
8420
9346
|
};
|
|
8421
9347
|
}
|
|
8422
9348
|
|
|
9349
|
+
// src/chat/scheduled-tasks/telemetry.ts
|
|
9350
|
+
function scheduledTaskAttributes(task, extras = {}) {
|
|
9351
|
+
return {
|
|
9352
|
+
"app.task.id": task.id,
|
|
9353
|
+
"app.task.type": "scheduled",
|
|
9354
|
+
"app.task.status": task.status,
|
|
9355
|
+
"app.task.schedule.kind": task.schedule.kind,
|
|
9356
|
+
"app.task.schedule.timezone": task.schedule.timezone,
|
|
9357
|
+
"app.task.credential_mode": task.credentialMode,
|
|
9358
|
+
"app.task.destination.channel_id": task.destination.channelId,
|
|
9359
|
+
"app.task.destination.team_id": task.destination.teamId,
|
|
9360
|
+
"app.task.destination.visibility": task.conversationAccess.visibility,
|
|
9361
|
+
"app.task.destination.audience": task.conversationAccess.audience,
|
|
9362
|
+
"messaging.destination.name": task.destination.channelId,
|
|
9363
|
+
...typeof task.nextRunAtMs === "number" ? { "app.task.next_run_at": new Date(task.nextRunAtMs).toISOString() } : {},
|
|
9364
|
+
...extras
|
|
9365
|
+
};
|
|
9366
|
+
}
|
|
9367
|
+
function scheduledTaskRunAttributes(task, run, extras = {}) {
|
|
9368
|
+
return scheduledTaskAttributes(task, {
|
|
9369
|
+
"app.task.run.id": run.id,
|
|
9370
|
+
"app.task.run.status": run.status,
|
|
9371
|
+
"app.task.run.scheduled_for": new Date(run.scheduledForMs).toISOString(),
|
|
9372
|
+
...run.dispatchId ? { "app.dispatch.id": run.dispatchId } : {},
|
|
9373
|
+
...run.resultMessageTs ? { "app.task.result_message_ts": run.resultMessageTs } : {},
|
|
9374
|
+
...extras
|
|
9375
|
+
});
|
|
9376
|
+
}
|
|
9377
|
+
function logScheduledTaskRunSkipped(task, run, extras = {}) {
|
|
9378
|
+
logInfo(
|
|
9379
|
+
"scheduled_task.run.skipped",
|
|
9380
|
+
scheduledTaskRunAttributes(task, run, extras)
|
|
9381
|
+
);
|
|
9382
|
+
}
|
|
9383
|
+
|
|
8423
9384
|
// src/chat/scheduled-tasks/tool-support.ts
|
|
8424
9385
|
import { createHash as createHash2 } from "crypto";
|
|
8425
9386
|
import {
|
|
@@ -8561,7 +9522,7 @@ function sameDestination(task, destination) {
|
|
|
8561
9522
|
}
|
|
8562
9523
|
async function getWritableTask(args) {
|
|
8563
9524
|
const destination = requireActiveConversation(args.context);
|
|
8564
|
-
const task = await
|
|
9525
|
+
const task = await readScheduledTask(getDb(), args.taskId);
|
|
8565
9526
|
if (!task || task.status === "deleted") {
|
|
8566
9527
|
throwToolInputError(
|
|
8567
9528
|
"Scheduled task was not found in the active Slack conversation."
|
|
@@ -8632,9 +9593,6 @@ function buildTaskId(args) {
|
|
|
8632
9593
|
).digest("hex").slice(0, 32);
|
|
8633
9594
|
return `${TASK_ID_PREFIX}_${digest}`;
|
|
8634
9595
|
}
|
|
8635
|
-
function schedulerStore(context) {
|
|
8636
|
-
return context.store;
|
|
8637
|
-
}
|
|
8638
9596
|
function normalizeStatus(value) {
|
|
8639
9597
|
if (value === "active" || value === "blocked") {
|
|
8640
9598
|
return value;
|
|
@@ -8684,13 +9642,13 @@ function createSlackScheduleCreateTaskTool(context) {
|
|
|
8684
9642
|
execute: async (input, options) => {
|
|
8685
9643
|
const destination = requireActiveConversation(context);
|
|
8686
9644
|
const actor = requireActor(context, destination);
|
|
8687
|
-
const store = schedulerStore(context);
|
|
8688
9645
|
const id = buildTaskId({
|
|
8689
9646
|
actor,
|
|
8690
9647
|
destination,
|
|
8691
9648
|
toolCallId: options.toolCallId
|
|
8692
9649
|
});
|
|
8693
|
-
const
|
|
9650
|
+
const db = getDb();
|
|
9651
|
+
const existing = await readScheduledTask(db, id);
|
|
8694
9652
|
if (existing) {
|
|
8695
9653
|
if (!sameDestination(existing, destination) || existing.createdBy.slackUserId !== actor.slackUserId) {
|
|
8696
9654
|
throwToolInputError("Scheduled task operation identity is invalid.");
|
|
@@ -8747,7 +9705,7 @@ function createSlackScheduleCreateTaskTool(context) {
|
|
|
8747
9705
|
},
|
|
8748
9706
|
...title ? { title } : {}
|
|
8749
9707
|
};
|
|
8750
|
-
const committed = await
|
|
9708
|
+
const committed = await createScheduledTask(db, task);
|
|
8751
9709
|
if (!sameDestination(committed, destination) || committed.createdBy.slackUserId !== actor.slackUserId) {
|
|
8752
9710
|
throwToolInputError("Scheduled task operation identity is invalid.");
|
|
8753
9711
|
}
|
|
@@ -8791,7 +9749,7 @@ function createSlackScheduleDeleteTaskTool(context) {
|
|
|
8791
9749
|
nextRunAtMs: void 0,
|
|
8792
9750
|
runNowAtMs: void 0
|
|
8793
9751
|
};
|
|
8794
|
-
await
|
|
9752
|
+
await saveScheduledTask(getDb(), next);
|
|
8795
9753
|
return scheduleTaskToolResult(
|
|
8796
9754
|
"slackScheduleDeleteTask",
|
|
8797
9755
|
compactTask(next)
|
|
@@ -8873,7 +9831,7 @@ function createSlackScheduleListTasksTool(context) {
|
|
|
8873
9831
|
}
|
|
8874
9832
|
const query = input.query?.trim() || void 0;
|
|
8875
9833
|
const crossConversation = Boolean(query) || channelId !== void 0 && channelId !== destination.channelId;
|
|
8876
|
-
const matching = (await
|
|
9834
|
+
const matching = (await listScheduledTasksForTeam(getDb(), destination.teamId)).filter((task) => {
|
|
8877
9835
|
if (task.destination.platform !== "slack") return false;
|
|
8878
9836
|
if (task.destination.teamId !== destination.teamId) return false;
|
|
8879
9837
|
if (task.status === "completed") return false;
|
|
@@ -8933,7 +9891,7 @@ function createSlackScheduleRunTaskNowTool(context) {
|
|
|
8933
9891
|
updatedAtMs: nowMs,
|
|
8934
9892
|
runNowAtMs: nowMs
|
|
8935
9893
|
};
|
|
8936
|
-
await
|
|
9894
|
+
await saveScheduledTask(getDb(), next);
|
|
8937
9895
|
return scheduleTaskToolResult(
|
|
8938
9896
|
"slackScheduleRunTaskNow",
|
|
8939
9897
|
compactTask(next)
|
|
@@ -8944,6 +9902,7 @@ function createSlackScheduleRunTaskNowTool(context) {
|
|
|
8944
9902
|
|
|
8945
9903
|
// src/chat/scheduled-tasks/tools/update.ts
|
|
8946
9904
|
import { z as z33 } from "zod";
|
|
9905
|
+
import { and, eq, inArray } from "drizzle-orm";
|
|
8947
9906
|
function createSlackScheduleUpdateTaskTool(context) {
|
|
8948
9907
|
return zodTool({
|
|
8949
9908
|
approvalMode: "review",
|
|
@@ -8973,8 +9932,8 @@ function createSlackScheduleUpdateTaskTool(context) {
|
|
|
8973
9932
|
execute: async (input) => {
|
|
8974
9933
|
const activeDestination = requireActiveConversation(context);
|
|
8975
9934
|
const actor = requireActor(context, activeDestination);
|
|
8976
|
-
const
|
|
8977
|
-
const lookup = await
|
|
9935
|
+
const db = getDb();
|
|
9936
|
+
const lookup = await readScheduledTask(db, input.task_id);
|
|
8978
9937
|
if (!lookup || lookup.status === "deleted") {
|
|
8979
9938
|
throwToolInputError("Scheduled task was not found.");
|
|
8980
9939
|
}
|
|
@@ -9011,7 +9970,12 @@ function createSlackScheduleUpdateTaskTool(context) {
|
|
|
9011
9970
|
}
|
|
9012
9971
|
const changingDestination = moveHere && !alreadyHere;
|
|
9013
9972
|
if (changingDestination) {
|
|
9014
|
-
const incompleteRuns = await
|
|
9973
|
+
const incompleteRuns = await db.select({ id: juniorSchedulerRuns.id }).from(juniorSchedulerRuns).where(
|
|
9974
|
+
and(
|
|
9975
|
+
eq(juniorSchedulerRuns.taskId, lookup.id),
|
|
9976
|
+
inArray(juniorSchedulerRuns.status, ["pending", "running"])
|
|
9977
|
+
)
|
|
9978
|
+
).limit(1);
|
|
9015
9979
|
if (incompleteRuns.length > 0) {
|
|
9016
9980
|
throwToolInputError(
|
|
9017
9981
|
"Scheduled task cannot be moved while an occurrence is already running. Try again after the current run finishes."
|
|
@@ -9077,15 +10041,8 @@ function createSlackScheduleUpdateTaskTool(context) {
|
|
|
9077
10041
|
compactTask(lookup)
|
|
9078
10042
|
);
|
|
9079
10043
|
}
|
|
9080
|
-
await
|
|
9081
|
-
const committed = await store.getTask(input.task_id);
|
|
9082
|
-
if (!committed) {
|
|
9083
|
-
throwToolInputError("Scheduled task update did not complete.");
|
|
9084
|
-
}
|
|
10044
|
+
const committed = await saveScheduledTask(db, next);
|
|
9085
10045
|
if (changingDestination) {
|
|
9086
|
-
if (!sameDestination(committed, activeDestination)) {
|
|
9087
|
-
throwToolInputError("Scheduled task move did not complete.");
|
|
9088
|
-
}
|
|
9089
10046
|
logInfo(
|
|
9090
10047
|
"scheduled_task.move.completed",
|
|
9091
10048
|
scheduledTaskAttributes(committed)
|
|
@@ -9107,7 +10064,6 @@ function scheduledTaskToolContext(context) {
|
|
|
9107
10064
|
return {
|
|
9108
10065
|
actor: context.actor,
|
|
9109
10066
|
source: context.source,
|
|
9110
|
-
store: createSchedulerSqlStore(getDb()),
|
|
9111
10067
|
users: { resolveActor: context.resolveActorIdentity },
|
|
9112
10068
|
...context.userText ? { userText: context.userText } : {}
|
|
9113
10069
|
};
|
|
@@ -11730,7 +12686,7 @@ function createSlackThreadReadTool(context) {
|
|
|
11730
12686
|
}
|
|
11731
12687
|
|
|
11732
12688
|
// src/chat/tools/user-lookup.ts
|
|
11733
|
-
import { and, eq, or, sql } from "drizzle-orm";
|
|
12689
|
+
import { and as and2, eq as eq2, or, sql } from "drizzle-orm";
|
|
11734
12690
|
import { alias } from "drizzle-orm/pg-core";
|
|
11735
12691
|
import { z as z53 } from "zod";
|
|
11736
12692
|
var linkedSlackIdentity = alias(juniorIdentities, "user_lookup_slack_identity");
|
|
@@ -11796,12 +12752,12 @@ async function storedSlackUserByEmail(teamId, email) {
|
|
|
11796
12752
|
const emailNormalized = normalizeIdentityEmail(email);
|
|
11797
12753
|
if (!emailNormalized) return void 0;
|
|
11798
12754
|
const identities = await getSqlExecutor().db().select().from(juniorIdentities).where(
|
|
11799
|
-
|
|
11800
|
-
|
|
11801
|
-
|
|
11802
|
-
|
|
11803
|
-
|
|
11804
|
-
|
|
12755
|
+
and2(
|
|
12756
|
+
eq2(juniorIdentities.provider, "slack"),
|
|
12757
|
+
eq2(juniorIdentities.providerTenantId, teamId),
|
|
12758
|
+
eq2(juniorIdentities.kind, "user"),
|
|
12759
|
+
eq2(juniorIdentities.emailVerified, true),
|
|
12760
|
+
eq2(juniorIdentities.emailNormalized, emailNormalized)
|
|
11805
12761
|
)
|
|
11806
12762
|
).limit(2);
|
|
11807
12763
|
if (identities.length > 1) {
|
|
@@ -11853,18 +12809,18 @@ async function resolveSlack(teamId, query) {
|
|
|
11853
12809
|
async function resolveStoredProvider(teamId, provider, query) {
|
|
11854
12810
|
const rows = await getSqlExecutor().db().select({ slack: linkedSlackIdentity }).from(juniorIdentities).innerJoin(
|
|
11855
12811
|
linkedSlackIdentity,
|
|
11856
|
-
|
|
11857
|
-
|
|
11858
|
-
|
|
11859
|
-
|
|
11860
|
-
|
|
12812
|
+
and2(
|
|
12813
|
+
eq2(linkedSlackIdentity.userId, juniorIdentities.userId),
|
|
12814
|
+
eq2(linkedSlackIdentity.kind, "user"),
|
|
12815
|
+
eq2(linkedSlackIdentity.provider, "slack"),
|
|
12816
|
+
eq2(linkedSlackIdentity.providerTenantId, teamId)
|
|
11861
12817
|
)
|
|
11862
12818
|
).where(
|
|
11863
|
-
|
|
11864
|
-
|
|
11865
|
-
|
|
12819
|
+
and2(
|
|
12820
|
+
eq2(juniorIdentities.kind, "user"),
|
|
12821
|
+
eq2(juniorIdentities.provider, provider),
|
|
11866
12822
|
or(
|
|
11867
|
-
|
|
12823
|
+
eq2(juniorIdentities.providerSubjectId, query),
|
|
11868
12824
|
sql`lower(${juniorIdentities.handle}) = lower(${query})`
|
|
11869
12825
|
)
|
|
11870
12826
|
)
|
|
@@ -11997,8 +12953,8 @@ function getTimeZoneOffsetMinutes(timestampMs, timezone) {
|
|
|
11997
12953
|
import { z as z55 } from "zod";
|
|
11998
12954
|
|
|
11999
12955
|
// src/chat/artifacts/store.ts
|
|
12000
|
-
import { createHash as createHash4, randomUUID as
|
|
12001
|
-
import { and as
|
|
12956
|
+
import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
|
|
12957
|
+
import { and as and3, eq as eq3, isNull } from "drizzle-orm";
|
|
12002
12958
|
var MAX_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
12003
12959
|
var IMAGE_TYPES = {
|
|
12004
12960
|
gif: "image/gif",
|
|
@@ -12085,12 +13041,12 @@ async function publishImage(args) {
|
|
|
12085
13041
|
const [existing] = await args.db.db().select({
|
|
12086
13042
|
id: juniorArtifacts.id
|
|
12087
13043
|
}).from(juniorArtifacts).where(
|
|
12088
|
-
|
|
12089
|
-
|
|
12090
|
-
|
|
13044
|
+
and3(
|
|
13045
|
+
eq3(juniorArtifacts.conversationId, args.conversationId),
|
|
13046
|
+
eq3(juniorArtifacts.sha256, sha256)
|
|
12091
13047
|
)
|
|
12092
13048
|
);
|
|
12093
|
-
const id = existing?.id ??
|
|
13049
|
+
const id = existing?.id ?? randomUUID5();
|
|
12094
13050
|
const filename = `${id}.${imageType.extension}`;
|
|
12095
13051
|
const storageKey = artifactKey(filename);
|
|
12096
13052
|
await args.storage.put({
|
|
@@ -12148,9 +13104,9 @@ async function unpublishArtifact(args) {
|
|
|
12148
13104
|
}
|
|
12149
13105
|
const now = args.now ?? /* @__PURE__ */ new Date();
|
|
12150
13106
|
const updated = await args.db.db().update(juniorArtifacts).set({ deleteRequestedAt: now }).where(
|
|
12151
|
-
|
|
12152
|
-
|
|
12153
|
-
|
|
13107
|
+
and3(
|
|
13108
|
+
eq3(juniorArtifacts.id, parsed.id),
|
|
13109
|
+
eq3(juniorArtifacts.conversationId, args.conversationId)
|
|
12154
13110
|
)
|
|
12155
13111
|
).returning({ id: juniorArtifacts.id });
|
|
12156
13112
|
if (updated.length === 0) {
|
|
@@ -12167,9 +13123,9 @@ async function readPublicArtifact(args) {
|
|
|
12167
13123
|
contentType: juniorArtifacts.contentType,
|
|
12168
13124
|
storageKey: juniorArtifacts.storageKey
|
|
12169
13125
|
}).from(juniorArtifacts).where(
|
|
12170
|
-
|
|
12171
|
-
|
|
12172
|
-
|
|
13126
|
+
and3(
|
|
13127
|
+
eq3(juniorArtifacts.id, parsed.id),
|
|
13128
|
+
eq3(juniorArtifacts.public, true),
|
|
12173
13129
|
isNull(juniorArtifacts.deleteRequestedAt)
|
|
12174
13130
|
)
|
|
12175
13131
|
);
|
|
@@ -13443,7 +14399,18 @@ function createWorkspaceTools(context) {
|
|
|
13443
14399
|
const workspace = await getWorkspaceByName(getDb(), name);
|
|
13444
14400
|
if (!workspace)
|
|
13445
14401
|
throw new ToolInputError(`Workspace not found: ${name}`);
|
|
13446
|
-
|
|
14402
|
+
try {
|
|
14403
|
+
await context.workspaces.switch(workspace, options.signal);
|
|
14404
|
+
} catch (error) {
|
|
14405
|
+
if (isWorkspaceSnapshotWaitingError(error)) {
|
|
14406
|
+
return {
|
|
14407
|
+
workspace: view(workspace),
|
|
14408
|
+
waiting: "workspace_snapshot",
|
|
14409
|
+
timed_out: true
|
|
14410
|
+
};
|
|
14411
|
+
}
|
|
14412
|
+
throw error;
|
|
14413
|
+
}
|
|
13447
14414
|
await tryRecordWorkspaceSwitchStat(workspace);
|
|
13448
14415
|
return { workspace: view(workspace) };
|
|
13449
14416
|
}
|
|
@@ -14963,7 +15930,7 @@ function createMcpAuthOrchestration(input) {
|
|
|
14963
15930
|
}
|
|
14964
15931
|
|
|
14965
15932
|
// src/chat/egress/plugin.ts
|
|
14966
|
-
import { randomUUID as
|
|
15933
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
14967
15934
|
|
|
14968
15935
|
// src/chat/sandbox/egress/credentials.ts
|
|
14969
15936
|
var HTTP_READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
@@ -15717,8 +16684,8 @@ async function executeCredentialedEgressRequest(input) {
|
|
|
15717
16684
|
function credentialContextForPluginEgress(credentialContext) {
|
|
15718
16685
|
return {
|
|
15719
16686
|
credentials: credentialContext,
|
|
15720
|
-
contextId:
|
|
15721
|
-
egressId: `plugin-egress:${
|
|
16687
|
+
contextId: randomUUID6(),
|
|
16688
|
+
egressId: `plugin-egress:${randomUUID6()}`,
|
|
15722
16689
|
expiresAtMs: Date.now() + 30 * 60 * 1e3
|
|
15723
16690
|
};
|
|
15724
16691
|
}
|
|
@@ -16151,6 +17118,7 @@ async function wireAgentTools(args) {
|
|
|
16151
17118
|
actor: args.currentActor,
|
|
16152
17119
|
locationConfiguration: args.run.environment?.locationConfiguration,
|
|
16153
17120
|
configurationValues: args.configurationValues,
|
|
17121
|
+
shouldYield: args.durability.shouldYield,
|
|
16154
17122
|
getActiveSkill: () => args.skillSandbox.getActiveSkill(),
|
|
16155
17123
|
prepareSandbox: pluginHooks.prepareSandbox,
|
|
16156
17124
|
prepareWorkspace: async (sandbox, recipe, signal) => await pluginHooks.prepareWorkspace(sandbox, recipe.repos, signal),
|
|
@@ -17470,7 +18438,10 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
|
|
|
17470
18438
|
history: run.history
|
|
17471
18439
|
});
|
|
17472
18440
|
const preAgentPromptMessages = () => existingSessionRecord?.piMessages ?? [...input.piMessages ?? []];
|
|
17473
|
-
const handoffEnabled = !isAgentRunFeatureDisabled(
|
|
18441
|
+
const handoffEnabled = !isAgentRunFeatureDisabled(
|
|
18442
|
+
policy.disabledFeatures,
|
|
18443
|
+
"handoff"
|
|
18444
|
+
);
|
|
17474
18445
|
const storedTurnRoute = await loadTurnRoute({ conversationId, turnId });
|
|
17475
18446
|
if (storedTurnRoute) {
|
|
17476
18447
|
const resumedAfterHandoff = handoffEnabled && activeModelProfile !== STANDARD_MODEL_PROFILE && activeModelProfile !== storedTurnRoute.modelProfile;
|
|
@@ -17943,10 +18914,14 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
|
|
|
17943
18914
|
const containsHandoff = toolCalls2.some(
|
|
17944
18915
|
(call) => call.name === HANDOFF_TOOL_NAME
|
|
17945
18916
|
);
|
|
17946
|
-
|
|
18917
|
+
const containsWorkspaceSwitch = toolCalls2.some(
|
|
18918
|
+
(call) => call.name === "switchWorkspace"
|
|
18919
|
+
);
|
|
18920
|
+
const exclusiveTool = containsHandoff ? HANDOFF_TOOL_NAME : containsWorkspaceSwitch ? "switchWorkspace" : void 0;
|
|
18921
|
+
if (exclusiveTool && toolCalls2.length !== 1) {
|
|
17947
18922
|
return {
|
|
17948
18923
|
block: true,
|
|
17949
|
-
reason:
|
|
18924
|
+
reason: `${exclusiveTool} must be the only tool call in its assistant message; reissue it alone`
|
|
17950
18925
|
};
|
|
17951
18926
|
}
|
|
17952
18927
|
return void 0;
|
|
@@ -17962,15 +18937,46 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
|
|
|
17962
18937
|
},
|
|
17963
18938
|
prepareNextTurnWithContext: async (nextTurn, hookSignal) => {
|
|
17964
18939
|
try {
|
|
18940
|
+
let continuedSnapshotWait = false;
|
|
18941
|
+
for (; ; ) {
|
|
18942
|
+
const snapshotWait = await continueWorkspaceSnapshotWait({
|
|
18943
|
+
messages: currentAgentMessages(),
|
|
18944
|
+
tools: toolsForActiveProfile(),
|
|
18945
|
+
signal: hookSignal
|
|
18946
|
+
});
|
|
18947
|
+
if (snapshotWait.kind === "none") break;
|
|
18948
|
+
if (snapshotWait.kind === "failed") {
|
|
18949
|
+
agent.state.messages = snapshotWait.messages;
|
|
18950
|
+
await runResume.persistSafeBoundary(snapshotWait.messages);
|
|
18951
|
+
throw snapshotWait.error;
|
|
18952
|
+
}
|
|
18953
|
+
continuedSnapshotWait = true;
|
|
18954
|
+
agent.state.messages = snapshotWait.messages;
|
|
18955
|
+
await runResume.persistSafeBoundary(snapshotWait.messages);
|
|
18956
|
+
if (snapshotWait.kind === "ready") break;
|
|
18957
|
+
const yieldError2 = runResume.prepareYieldIfDue(
|
|
18958
|
+
snapshotWait.messages
|
|
18959
|
+
);
|
|
18960
|
+
if (yieldError2) throw yieldError2;
|
|
18961
|
+
await sleep(5e3, hookSignal);
|
|
18962
|
+
}
|
|
17965
18963
|
const handoffUpdate = applyPendingHandoff();
|
|
18964
|
+
const snapshotWaitUpdate = continuedSnapshotWait ? {
|
|
18965
|
+
context: {
|
|
18966
|
+
...nextTurn.context,
|
|
18967
|
+
messages: currentAgentMessages(),
|
|
18968
|
+
tools: agent.state.tools
|
|
18969
|
+
}
|
|
18970
|
+
} : void 0;
|
|
17966
18971
|
const pendingMessages = await drainSteeringMessages();
|
|
17967
18972
|
const capacityUpdate = await applyActiveContextCompaction(
|
|
17968
|
-
handoffUpdate ? currentAgentMessages() : nextTurn.context.messages,
|
|
18973
|
+
handoffUpdate || continuedSnapshotWait ? currentAgentMessages() : nextTurn.context.messages,
|
|
17969
18974
|
hookSignal,
|
|
17970
18975
|
pendingMessages,
|
|
17971
18976
|
true
|
|
17972
18977
|
);
|
|
17973
|
-
const
|
|
18978
|
+
const handoffOrWaitUpdate = handoffUpdate ? { ...snapshotWaitUpdate, ...handoffUpdate } : snapshotWaitUpdate;
|
|
18979
|
+
const combinedUpdate = capacityUpdate ? { ...handoffOrWaitUpdate, ...capacityUpdate } : handoffOrWaitUpdate;
|
|
17974
18980
|
const repositoryInstructionsUpdate = await repositoryInstructionsContext.applyUpdate(
|
|
17975
18981
|
combinedUpdate,
|
|
17976
18982
|
nextTurn.context
|
|
@@ -18450,7 +19456,9 @@ export {
|
|
|
18450
19456
|
normalizeSandboxEgressTracePropagationDomains,
|
|
18451
19457
|
shouldPropagateSandboxEgressTrace,
|
|
18452
19458
|
resolveSandboxEgressProviderForHost,
|
|
18453
|
-
|
|
19459
|
+
getNextRunAtMs,
|
|
19460
|
+
scheduledTaskRunAttributes,
|
|
19461
|
+
logScheduledTaskRunSkipped,
|
|
18454
19462
|
listThreadReplies,
|
|
18455
19463
|
renderBlockText,
|
|
18456
19464
|
renderAttachmentText,
|