@leuduan/lagent 0.1.2 → 44.0.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/README.md +28 -15
- package/dist/chunks/{UC3FVVJ2.js → 2GCIWSOA.js} +2 -2
- package/dist/chunks/2P47AHNK.js +7 -0
- package/dist/chunks/3UMZMPWW.js +1 -0
- package/dist/chunks/5IRF47YB.js +1 -0
- package/dist/chunks/6DRCKZ2C.js +1 -0
- package/dist/chunks/6FRNHMCV.js +1 -0
- package/dist/chunks/7LI64H5X.js +17 -0
- package/dist/chunks/{SL2YLB2F.js → 7VAPNE3L.js} +1 -1
- package/dist/chunks/{BM3S37EZ.js → ASSQZH3F.js} +1 -1
- package/dist/chunks/B4FXK5ER.js +77 -0
- package/dist/chunks/{37YOGDGY.js → BBCW2FZK.js} +1 -1
- package/dist/chunks/BPRCULZH.js +1 -0
- package/dist/chunks/CANPZYTH.js +24 -0
- package/dist/chunks/CQEVREBT.js +1 -0
- package/dist/chunks/CUK7CGC7.js +1 -0
- package/dist/chunks/D4PQP5MA.js +3 -0
- package/dist/chunks/{CDQG7RKA.js → DDZ7YF26.js} +1 -1
- package/dist/chunks/EMRB4KCN.js +6 -0
- package/dist/chunks/EPYQ5M3B.js +3 -0
- package/dist/chunks/G44LG6VL.js +1 -0
- package/dist/chunks/GGSVIIWJ.js +2 -0
- package/dist/chunks/IAAEP6VL.js +30 -0
- package/dist/chunks/{AP6Q4EPT.js → JCTBX33H.js} +1 -1
- package/dist/chunks/JFX2MXBA.js +4 -0
- package/dist/chunks/JFZZXVWD.js +15 -0
- package/dist/chunks/KUZCL2SH.js +1 -0
- package/dist/chunks/MHC6VPB4.js +7 -0
- package/dist/chunks/MMNNZCBO.js +5 -0
- package/dist/chunks/{C5LYCXJG.js → MNJ4SRF7.js} +1 -1
- package/dist/chunks/N6IXG2VL.js +1 -0
- package/dist/chunks/OCBPT7KE.js +1 -0
- package/dist/chunks/OL5A6ITQ.js +1 -0
- package/dist/chunks/Q75FUN7D.js +2 -0
- package/dist/chunks/Q7S5LFGN.js +1 -0
- package/dist/chunks/RCIHV3KG.js +1 -0
- package/dist/chunks/S34ZA2R6.js +1 -0
- package/dist/chunks/SS3B5W34.js +12 -0
- package/dist/chunks/VYMT4TXD.js +1 -0
- package/dist/chunks/W57RW55W.js +1 -0
- package/dist/compositions/base.d.ts +11 -0
- package/dist/{presets → compositions}/codex.d.ts +12 -6
- package/dist/{presets/prompts.d.ts → compositions/codexPrompts.d.ts} +1 -1
- package/dist/context.d.ts +18 -10
- package/dist/index.d.ts +31 -23
- package/dist/index.js +1 -77
- package/dist/journal/catalog.d.ts +1 -1
- package/dist/journal/journal.d.ts +3 -3
- package/dist/journal/projection.d.ts +1 -1
- package/dist/journal/store.d.ts +3 -3
- package/dist/llm/index.d.ts +19 -13
- package/dist/llm/index.js +1 -0
- package/dist/llm/provider.d.ts +11 -0
- package/dist/loop/agent.d.ts +16 -132
- package/dist/loop/authorization.d.ts +41 -0
- package/dist/loop/auxiliary.d.ts +7 -3
- package/dist/loop/chunks.d.ts +3 -3
- package/dist/loop/completion.d.ts +17 -0
- package/dist/loop/errors.d.ts +6 -29
- package/dist/loop/hooks.d.ts +39 -6
- package/dist/loop/index.d.ts +7 -28
- package/dist/loop/runner.d.ts +134 -0
- package/dist/loop/runtime.d.ts +7 -16
- package/dist/loop/strategy.d.ts +12 -3
- package/dist/loop/{prepare.d.ts → surfacePrepare.d.ts} +2 -2
- package/dist/loop/tool.d.ts +11 -6
- package/dist/loop/toolCalls.d.ts +3 -10
- package/dist/plugin.d.ts +31 -34
- package/dist/plugin.js +1 -1
- package/dist/plugins/agentsMd.d.ts +24 -20
- package/dist/plugins/agentsMd.js +1 -1
- package/dist/plugins/ask/index.d.ts +2 -2
- package/dist/plugins/ask/index.js +1 -1
- package/dist/plugins/ask/service.d.ts +2 -2
- package/dist/plugins/commands/index.d.ts +5 -2
- package/dist/plugins/commands/index.js +1 -1
- package/dist/plugins/commands/output.d.ts +1 -1
- package/dist/plugins/commands/record.d.ts +31 -22
- package/dist/plugins/commands/service.d.ts +18 -11
- package/dist/plugins/commands/{background.d.ts → tools.d.ts} +2 -0
- package/dist/plugins/compaction/checkpoint.d.ts +4 -1
- package/dist/plugins/compaction/config.d.ts +3 -3
- package/dist/plugins/compaction/estimate.d.ts +9 -1
- package/dist/plugins/compaction/index.d.ts +87 -2
- package/dist/plugins/compaction/index.js +5 -5
- package/dist/plugins/compaction/{records.d.ts → record.d.ts} +36 -36
- package/dist/plugins/compaction/{log.d.ts → state.d.ts} +8 -1
- package/dist/plugins/dateAndBranch.js +1 -1
- package/dist/plugins/delegations/answers.d.ts +1 -1
- package/dist/plugins/delegations/assignmentKey.d.ts +1 -1
- package/dist/plugins/delegations/cleanup.d.ts +1 -1
- package/dist/plugins/delegations/index.js +1 -1
- package/dist/plugins/delegations/notices.d.ts +8 -8
- package/dist/plugins/delegations/obligations.d.ts +13 -13
- package/dist/plugins/delegations/record.d.ts +11 -10
- package/dist/plugins/delegations/request.d.ts +8 -5
- package/dist/plugins/delegations/role.d.ts +1 -1
- package/dist/plugins/delegations/service.d.ts +9 -8
- package/dist/plugins/delegations/{waitBook.d.ts → waitResolver.d.ts} +9 -9
- package/dist/plugins/diagnostics/digest.d.ts +1 -1
- package/dist/plugins/diagnostics/index.d.ts +1 -1
- package/dist/plugins/diagnostics/index.js +1 -1
- package/dist/plugins/fileTools/config.d.ts +10 -0
- package/dist/plugins/{tools → fileTools}/editFiles.d.ts +16 -27
- package/dist/plugins/fileTools/gitDiscovery.d.ts +9 -0
- package/dist/plugins/fileTools/index.d.ts +18 -0
- package/dist/plugins/fileTools/index.js +1 -0
- package/dist/plugins/fileTools/tools.d.ts +6 -0
- package/dist/plugins/historyRecall/history.d.ts +1 -1
- package/dist/plugins/historyRecall/index.js +1 -1
- package/dist/plugins/historyRecall/render.d.ts +1 -1
- package/dist/plugins/mcp/catalog.d.ts +1 -1
- package/dist/plugins/mcp/index.d.ts +2 -2
- package/dist/plugins/mcp/index.js +1 -1
- package/dist/plugins/mcp/mount.d.ts +2 -2
- package/dist/plugins/mcp/render.d.ts +1 -1
- package/dist/plugins/mcp/{tool.d.ts → tools.d.ts} +1 -1
- package/dist/plugins/{routeContext.d.ts → routeNotice.d.ts} +2 -2
- package/dist/plugins/routeNotice.js +1 -0
- package/dist/{sandbox → plugins/sandbox}/absent.d.ts +11 -10
- package/dist/{sandbox → plugins/sandbox}/index.d.ts +5 -2
- package/dist/plugins/sandbox/index.js +4 -0
- package/dist/{sandbox → plugins/sandbox}/mode.d.ts +5 -5
- package/dist/plugins/sandbox/policy.d.ts +163 -0
- package/dist/{sandbox → plugins/sandbox}/service.d.ts +23 -13
- package/dist/plugins/schedules/index.js +3 -3
- package/dist/plugins/schedules/record.d.ts +18 -18
- package/dist/plugins/schedules/service.d.ts +2 -2
- package/dist/plugins/skills/discovery.d.ts +38 -9
- package/dist/plugins/skills/index.d.ts +1 -1
- package/dist/plugins/skills/index.js +1 -1
- package/dist/plugins/subAgents/index.js +1 -1
- package/dist/plugins/subAgents/{fork.d.ts → tools.d.ts} +36 -1
- package/dist/plugins/threadDirs.d.ts +36 -0
- package/dist/plugins/threadDirs.js +1 -0
- package/dist/plugins/toolPolicy/index.d.ts +2 -2
- package/dist/plugins/toolPolicy/index.js +1 -1
- package/dist/plugins/truncateToolOutput.d.ts +14 -10
- package/dist/plugins/truncateToolOutput.js +6 -1
- package/dist/plugins/usage/index.d.ts +4 -2
- package/dist/plugins/usage/index.js +1 -1
- package/dist/plugins/usage/tracker.d.ts +1 -1
- package/dist/plugins/verify/index.d.ts +7 -55
- package/dist/plugins/verify/index.js +6 -1
- package/dist/plugins/verify/judgement.d.ts +16 -0
- package/dist/plugins/verify/record.d.ts +382 -0
- package/dist/plugins/verify/service.d.ts +7 -4
- package/dist/plugins/verify/state.d.ts +58 -0
- package/dist/plugins/webTools/index.d.ts +34 -0
- package/dist/plugins/webTools/index.js +1 -0
- package/dist/plugins/workflows/assignment.d.ts +6 -2
- package/dist/plugins/workflows/cleanup.d.ts +1 -1
- package/dist/plugins/workflows/definition.d.ts +2 -2
- package/dist/plugins/workflows/{bindings.d.ts → entryPlan.d.ts} +2 -2
- package/dist/plugins/workflows/{providers.d.ts → hostBindings.d.ts} +2 -2
- package/dist/plugins/workflows/index.d.ts +4 -4
- package/dist/plugins/workflows/index.js +10 -5
- package/dist/plugins/workflows/observations.d.ts +3 -3
- package/dist/plugins/workflows/participantModels.d.ts +28 -0
- package/dist/plugins/workflows/participants.d.ts +2 -2
- package/dist/plugins/workflows/policy.d.ts +2 -2
- package/dist/plugins/workflows/record.d.ts +51 -65
- package/dist/plugins/workflows/routing.d.ts +2 -2
- package/dist/plugins/workflows/service.d.ts +24 -24
- package/dist/plugins/workflows/{fold.d.ts → state.d.ts} +2 -2
- package/dist/plugins/workflows/stopCheck.d.ts +13 -0
- package/dist/plugins/workflows/tools.d.ts +13 -0
- package/dist/plugins/workflows/view.d.ts +2 -2
- package/dist/plugins/{workspaceContext.d.ts → workspaceNotice.d.ts} +5 -7
- package/dist/plugins/workspaceNotice.js +1 -0
- package/dist/{plugins/providers → providers}/anthropic.d.ts +6 -3
- package/dist/{plugins/providers → providers}/anthropicMessages.d.ts +2 -1
- package/dist/{plugins/providers → providers}/codex.d.ts +14 -3
- package/dist/{plugins/providers → providers}/deepseek.d.ts +6 -2
- package/dist/{plugins/providers → providers}/failure.d.ts +1 -1
- package/dist/{plugins/providers → providers}/gateway.d.ts +2 -1
- package/dist/providers/index.d.ts +18 -0
- package/dist/providers/index.js +6 -0
- package/dist/{plugins/providers → providers}/openaiChat.d.ts +13 -2
- package/dist/{plugins/providers → providers}/openaiResponses.d.ts +12 -3
- package/dist/{plugins/providers → providers}/opencodeCopilot.d.ts +2 -2
- package/dist/{plugins/providers → providers}/opencodeGo.d.ts +18 -7
- package/dist/{plugins/providers → providers}/shared.d.ts +9 -1
- package/dist/{plugins/providers → providers}/stream.d.ts +4 -2
- package/dist/{models/door.d.ts → records/index.d.ts} +8 -7
- package/dist/records/index.js +1 -0
- package/dist/{models/wire → records}/journal.d.ts +210 -14
- package/dist/{models/wire/primitives.d.ts → records/toolResult.d.ts} +1 -1
- package/dist/services/agents.d.ts +26 -1
- package/dist/services/confinement/files.d.ts +118 -0
- package/dist/services/confinement/service.d.ts +219 -0
- package/dist/services/core.d.ts +2 -8
- package/dist/{kit/extension.d.ts → services/extensionEvents.d.ts} +5 -4
- package/dist/services/journal.d.ts +1 -1
- package/dist/services/llm.d.ts +4 -2
- package/dist/{kit → services}/modes.d.ts +1 -1
- package/dist/services/plugins.d.ts +34 -0
- package/dist/services/{preset.d.ts → presets/definition.d.ts} +4 -4
- package/dist/services/{presets.d.ts → presets/installations.d.ts} +3 -3
- package/dist/services/prompts.d.ts +23 -5
- package/dist/services/{threadActivation.d.ts → threads/activation.d.ts} +3 -3
- package/dist/services/{admission.d.ts → threads/admission.d.ts} +3 -3
- package/dist/services/{birth.d.ts → threads/birth.d.ts} +9 -10
- package/dist/services/threads/index.d.ts +14 -0
- package/dist/services/{runEvents.d.ts → threads/runEvents.d.ts} +2 -2
- package/dist/services/{owner.d.ts → threads/runOwner.d.ts} +5 -4
- package/dist/services/{threads.d.ts → threads/service.d.ts} +9 -8
- package/dist/services/{thread.d.ts → threads/thread.d.ts} +8 -7
- package/dist/services/{threadView.d.ts → threads/view.d.ts} +7 -6
- package/dist/services/tools.d.ts +79 -2
- package/dist/{loop/opening.d.ts → state/birth.d.ts} +22 -2
- package/dist/{loop → state}/chain.d.ts +3 -3
- package/dist/state/confirmations.d.ts +64 -0
- package/dist/{loop → state}/continuation.d.ts +3 -2
- package/dist/{loop → state}/decide.d.ts +10 -2
- package/dist/state/dispatches.d.ts +54 -0
- package/dist/state/errors.d.ts +26 -0
- package/dist/{loop → state}/forks.d.ts +1 -1
- package/dist/{loop → state}/holds.d.ts +1 -1
- package/dist/{loop → state}/inbox.d.ts +2 -2
- package/dist/state/index.d.ts +31 -0
- package/dist/state/inputs.d.ts +58 -0
- package/dist/state/installations.d.ts +28 -0
- package/dist/state/prompt.d.ts +13 -0
- package/dist/state/recovery.d.ts +7 -0
- package/dist/{loop → state}/replace.d.ts +2 -2
- package/dist/{loop → state}/requests.d.ts +3 -3
- package/dist/{loop → state}/route.d.ts +10 -2
- package/dist/state/summary.d.ts +16 -0
- package/dist/{loop → state}/surface.d.ts +3 -3
- package/dist/state/surfaceRead.d.ts +20 -0
- package/dist/{loop/threadState.d.ts → state/thread.d.ts} +12 -11
- package/dist/state/turns.d.ts +87 -0
- package/dist/state/waits.d.ts +38 -0
- package/dist/{loop → state}/wake.d.ts +1 -1
- package/dist/stores/durableObject.js +1 -0
- package/dist/{plugins/store → stores}/memory.d.ts +2 -2
- package/dist/{plugins/store → stores}/sql.d.ts +2 -2
- package/dist/stores/sqlite.js +1 -0
- package/dist/sys/environment.d.ts +0 -5
- package/dist/sys/git.d.ts +0 -13
- package/dist/sys/paths.d.ts +0 -47
- package/dist/sys/spawn.d.ts +16 -6
- package/dist/testing/clock.d.ts +1 -1
- package/dist/testing/fakeLlm.d.ts +4 -2
- package/dist/testing/fedLlm.d.ts +3 -1
- package/dist/testing/helpers.d.ts +31 -13
- package/dist/testing/index.js +1 -1
- package/dist/testing/settlement.d.ts +1 -1
- package/package.json +36 -28
- package/dist/chunks/2O7ZGEIT.js +0 -1
- package/dist/chunks/2OSESBIL.js +0 -1
- package/dist/chunks/3LQHQRIU.js +0 -6
- package/dist/chunks/3QCW4WDJ.js +0 -1
- package/dist/chunks/4XVRUSCQ.js +0 -1
- package/dist/chunks/6ECI2GGV.js +0 -1
- package/dist/chunks/74HKUGRR.js +0 -1
- package/dist/chunks/ADHERZLW.js +0 -2
- package/dist/chunks/D7E6EWIE.js +0 -3
- package/dist/chunks/DRYAN665.js +0 -1
- package/dist/chunks/DXFXAYDN.js +0 -17
- package/dist/chunks/EGZI4XQX.js +0 -24
- package/dist/chunks/ENWFSU27.js +0 -41
- package/dist/chunks/G6RXC3AL.js +0 -1
- package/dist/chunks/G74AGRAJ.js +0 -15
- package/dist/chunks/GQGNVSKB.js +0 -1
- package/dist/chunks/IJ2WNMD5.js +0 -7
- package/dist/chunks/JPZFKSWT.js +0 -1
- package/dist/chunks/KESCTXGO.js +0 -5
- package/dist/chunks/L3N4FIEN.js +0 -1
- package/dist/chunks/NAOUTU7A.js +0 -4
- package/dist/chunks/NDIM7MEW.js +0 -1
- package/dist/chunks/O4PPOM5Z.js +0 -2
- package/dist/chunks/OVZFGILJ.js +0 -1
- package/dist/chunks/P5ZHHOOM.js +0 -6
- package/dist/chunks/QMMLQJBS.js +0 -1
- package/dist/chunks/RK3VP2Q4.js +0 -3
- package/dist/chunks/SGGMBSBA.js +0 -1
- package/dist/chunks/TOOIB6J2.js +0 -1
- package/dist/chunks/TYTQRLTX.js +0 -1
- package/dist/chunks/UCTEU62F.js +0 -1
- package/dist/chunks/UD3VJER6.js +0 -1
- package/dist/chunks/WUUKTSRT.js +0 -7
- package/dist/chunks/X65X4KP5.js +0 -7
- package/dist/chunks/YIR5MYGU.js +0 -1
- package/dist/chunks/YLNJXQ3Z.js +0 -1
- package/dist/kit/claim.d.ts +0 -13
- package/dist/kit/drain.d.ts +0 -10
- package/dist/kit/grants.d.ts +0 -9
- package/dist/kit/scope.d.ts +0 -32
- package/dist/kit/service.d.ts +0 -7
- package/dist/kit/strategy.d.ts +0 -10
- package/dist/kit/tool.d.ts +0 -72
- package/dist/llm/door.d.ts +0 -21
- package/dist/llm/door.js +0 -1
- package/dist/loop/projections.d.ts +0 -307
- package/dist/loop/state.d.ts +0 -70
- package/dist/loop/summary.d.ts +0 -3
- package/dist/models/config/agent.d.ts +0 -18
- package/dist/models/config/prompt.d.ts +0 -18
- package/dist/models/door.js +0 -1
- package/dist/models/index.d.ts +0 -7
- package/dist/plugins/commands/foreground.d.ts +0 -4
- package/dist/plugins/compaction/preset.d.ts +0 -86
- package/dist/plugins/compaction/shared.d.ts +0 -14
- package/dist/plugins/delegations/testing.d.ts +0 -16
- package/dist/plugins/providers/door.d.ts +0 -61
- package/dist/plugins/providers/door.js +0 -1
- package/dist/plugins/providers/index.d.ts +0 -6
- package/dist/plugins/routeContext.js +0 -1
- package/dist/plugins/store/durableObject.js +0 -1
- package/dist/plugins/store/sqlite.js +0 -1
- package/dist/plugins/subAgents/tool.d.ts +0 -38
- package/dist/plugins/tools/index.d.ts +0 -59
- package/dist/plugins/tools/index.js +0 -1
- package/dist/plugins/verify/facts.d.ts +0 -356
- package/dist/plugins/workflows/submit.d.ts +0 -8
- package/dist/plugins/workspaceContext.js +0 -1
- package/dist/presets/agents.d.ts +0 -7
- package/dist/presets/base.d.ts +0 -10
- package/dist/presets/index.d.ts +0 -4
- package/dist/sandbox/index.js +0 -4
- package/dist/sandbox/policy.d.ts +0 -86
- package/dist/services/launches.d.ts +0 -104
- package/dist/services/threadDirs.d.ts +0 -38
- package/dist/testing/sandbox.d.ts +0 -71
- /package/dist/chunks/{UKXMCHHT.js → FJLU4UH3.js} +0 -0
- /package/dist/chunks/{J5S4URAW.js → HK6ZSONF.js} +0 -0
- /package/dist/plugins/ask/{askQuestion.d.ts → tools.d.ts} +0 -0
- /package/dist/plugins/delegations/{waits.d.ts → waitContracts.d.ts} +0 -0
- /package/dist/plugins/{tools → fileTools}/fsRead.d.ts +0 -0
- /package/dist/plugins/{tools → fileTools}/ripgrep.d.ts +0 -0
- /package/dist/plugins/{tools → fileTools}/textEdit.d.ts +0 -0
- /package/dist/{util → plugins/mcp}/version.d.ts +0 -0
- /package/dist/{sandbox → plugins/sandbox}/client.d.ts +0 -0
- /package/dist/{sandbox → plugins/sandbox}/files.d.ts +0 -0
- /package/dist/{sandbox → plugins/sandbox}/protocol.d.ts +0 -0
- /package/dist/{sandbox → plugins/sandbox}/worker.d.ts +0 -0
- /package/dist/{sandbox → plugins/sandbox}/worker.js +0 -0
- /package/dist/plugins/skills/{tool.d.ts → tools.d.ts} +0 -0
- /package/dist/plugins/{tools → webTools}/html.d.ts +0 -0
- /package/dist/plugins/{tools → webTools}/network.d.ts +0 -0
- /package/dist/plugins/{tools → webTools}/web.d.ts +0 -0
- /package/dist/plugins/workflows/{work.d.ts → workStatus.d.ts} +0 -0
- /package/dist/{plugins/providers → providers}/codexAuth.d.ts +0 -0
- /package/dist/services/{contributions.d.ts → catalogContributions.d.ts} +0 -0
- /package/dist/{loop → state}/limits.d.ts +0 -0
- /package/dist/{plugins/store → stores}/durableObject.d.ts +0 -0
- /package/dist/{plugins/store → stores}/durableObjectConnection.d.ts +0 -0
- /package/dist/{plugins/store → stores}/sqlite.d.ts +0 -0
- /package/dist/{models → util}/jsonSchema.d.ts +0 -0
- /package/dist/{kit → util}/retries.d.ts +0 -0
- /package/dist/{kit → util}/timers.d.ts +0 -0
|
@@ -7,22 +7,23 @@ export type SandboxAbsentConfig = z.input<typeof AbsentConfigSchema>;
|
|
|
7
7
|
/**
|
|
8
8
|
* For a host that records sandbox modes but started without the sandbox. A process with no launch
|
|
9
9
|
* handler would run every command plain, so a conversation recorded as confined would run unconfined
|
|
10
|
-
* silently after such a restart. This is the
|
|
11
|
-
* which is what makes the refusal cover every caller of the seam, `commands
|
|
12
|
-
* `ctx.
|
|
13
|
-
* message for the model, and a launch for one recorded as unconfined, or
|
|
14
|
-
* plain `/bin/sh -c`
|
|
10
|
+
* silently after such a restart. This is the confinement handler of core's `ctx.confinement` for that
|
|
11
|
+
* host, which is what makes the refusal cover every caller of the seam, `commands`, the search tools
|
|
12
|
+
* and a host calling `ctx.confinement` directly alike: an operation for a conversation recorded as
|
|
13
|
+
* confined is refused with a message for the model, and a launch for one recorded as unconfined, or
|
|
14
|
+
* with no mode at all, runs plain (`/bin/sh -c` for shell text, the argv itself otherwise) with
|
|
15
|
+
* provenance that says so (`unconfined`, under this plugin's own launcher),
|
|
15
16
|
* which a host's display marks. The model is told through the sandbox's notice: the unavailable
|
|
16
17
|
* guidance when confined, and the lift a restart may have left owed otherwise.
|
|
17
18
|
*
|
|
18
|
-
* The refusal happens
|
|
19
|
+
* The refusal happens at the operation's first use, after any confirmation the tool call needed, so
|
|
19
20
|
* a call a person approved can still be refused: rare, since the notice says so at the first step.
|
|
20
21
|
* It does not cover what spawns outside the seam: MCP stdio servers and lagent's own spawns.
|
|
21
22
|
*
|
|
22
|
-
* The recorded mode is read once,
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* The recorded mode is read once, at the operation's first use, from the journal's fold; core holds
|
|
24
|
+
* the histories it is decided from for the snapshot. A thread the journal does not hold, a caller
|
|
25
|
+
* chain that does not reach its conversation root and a mode row this build cannot read refuse the
|
|
26
|
+
* operation with that error, never allow it.
|
|
26
27
|
*
|
|
27
28
|
* Mount it unscoped, after `core` and before threads start, so a failed composition is refused before
|
|
28
29
|
* recovery. It excludes `sandbox` in either mount order. Disposing it releases its handler, which
|
|
@@ -6,7 +6,7 @@ export { SandboxConfigSchema, type SandboxConfigInput, type SandboxHealth, type
|
|
|
6
6
|
/**
|
|
7
7
|
* An optional OS sandbox for `run_command` and `start_command`, through Anthropic's sandbox-runtime
|
|
8
8
|
* (`@anthropic-ai/sandbox-runtime`, an optional peer dependency: only the worker module loads it).
|
|
9
|
-
* Mounting `sandbox` registers it as the
|
|
9
|
+
* Mounting `sandbox` registers it as the confinement handler of core's `ctx.confinement`, so everything
|
|
10
10
|
* `commands` launches afterwards goes through it, and each confined launch is limited to writing its thread's working directory, the host's
|
|
11
11
|
* extra roots and a per-thread temporary directory, and, in `allowlist` network mode, to reaching the
|
|
12
12
|
* host's allowed domains (`unrestricted` leaves the network open: only the filesystem rules bind). A
|
|
@@ -37,7 +37,10 @@ export { SandboxConfigSchema, type SandboxConfigInput, type SandboxHealth, type
|
|
|
37
37
|
* await root.plugin(commands, { environment })
|
|
38
38
|
* ```
|
|
39
39
|
*
|
|
40
|
-
*
|
|
40
|
+
* One filesystem policy per conversation, enforced two ways: srt confines the processes `commands` and
|
|
41
|
+
* the search tools launch, and the same policy is checked in process for the file tools and the readers
|
|
42
|
+
* of instruction files and skills, through the confinement operation's file facade. Other spawns are
|
|
43
|
+
* unchanged. macOS only for now.
|
|
41
44
|
*/
|
|
42
45
|
export declare const sandbox: Plugin.Constructor<SandboxConfigInput>;
|
|
43
46
|
/** The worker entry built beside this module, for a host that runs lagent from `dist`. */
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{a as p}from"../../chunks/VYMT4TXD.js";import{b as D,c as R,d as B,e as te}from"../../chunks/6DRCKZ2C.js";import{d as ne}from"../../chunks/7LI64H5X.js";import"../../chunks/3UMZMPWW.js";import{a as ie,b as ae}from"../../chunks/DDZ7YF26.js";import{d as N}from"../../chunks/S34ZA2R6.js";import{b as re,c as oe,e as se}from"../../chunks/6FRNHMCV.js";import"../../chunks/CUK7CGC7.js";import"../../chunks/BPRCULZH.js";import{d as E}from"../../chunks/DDANMY45.js";import{c as v}from"../../chunks/BBCW2FZK.js";import"../../chunks/MMNNZCBO.js";import"../../chunks/G44LG6VL.js";import"../../chunks/D4PQP5MA.js";import{createRequire as bn}from"node:module";import{dirname as vn,join as En}from"node:path";import{randomUUID as dn}from"node:crypto";import{homedir as hn}from"node:os";import{isAbsolute as U}from"node:path";import{Service as Pe}from"@leuduan/cordis";import{z as d}from"zod";import{Worker as He}from"node:worker_threads";var O=class{worker;pending=new Map;nextId=1;ended=null;closing=!1;onDeath;constructor(e,n,o){this.onDeath=o,this.worker=new He(e,{env:n}),this.worker.on("message",r=>this.settle(r)),this.worker.on("error",r=>this.end(new Error(`The sandbox worker failed: ${r.message}`))),this.worker.on("exit",r=>this.end(new Error(`The sandbox worker exited with code ${r}`)))}get alive(){return this.ended===null&&!this.closing}request(e,n){if(this.ended!==null)return Promise.reject(this.ended);if(this.closing)return Promise.reject(new Error("The sandbox worker is shutting down"));let{signal:o}=n;if(o?.aborted)return Promise.reject(o.reason instanceof Error?o.reason:new Error("aborted"));let r=this.nextId++;return new Promise((s,i)=>{let a=m=>{this.pending.delete(r)&&(clearTimeout(c),l(),e.type==="wrap"&&this.send({type:"cancel",id:r}),i(m))},h=()=>a(o?.reason instanceof Error?o.reason:new Error("aborted")),c=setTimeout(()=>a(new Error(`The sandbox worker did not answer ${e.type} within ${n.timeoutMs} ms`)),n.timeoutMs),l=()=>o?.removeEventListener("abort",h);o?.addEventListener("abort",h,{once:!0}),this.pending.set(r,{resolve:s,reject:i,timer:c,cleanup:l}),this.send({...e,id:r})})}async terminate(){this.closing=!0,this.rejectAll(new Error("The sandbox worker is shutting down")),await this.worker.terminate()}unref(){this.worker.unref()}send(e){try{this.worker.postMessage(e)}catch(n){this.end(new Error(`The sandbox worker could not be reached: ${n instanceof Error?n.message:n}`))}}settle(e){let n=this.pending.get(e.id);n!==void 0&&(this.pending.delete(e.id),clearTimeout(n.timer),n.cleanup(),e.ok?n.resolve(e.value):n.reject(new Error(e.error)))}end(e){this.ended===null&&(this.ended=e,this.rejectAll(e),this.closing||this.onDeath(e))}rejectAll(e){let n=[...this.pending.values()];this.pending.clear();for(let o of n)clearTimeout(o.timer),o.cleanup(),o.reject(e)}};import{createHash as I,randomUUID as Ue}from"node:crypto";import{existsSync as We,linkSync as Be,lstatSync as de,mkdirSync as he,readdirSync as Ie,readFileSync as ce,unlinkSync as ue,writeFileSync as Fe}from"node:fs";import{join as T}from"node:path";var le=t=>T(t,".srt"),G=(t,e)=>T(t,"scratch",I("sha256").update(e).digest("hex"));function F(t){try{he(t,{mode:448})}catch(n){if(!E(n,"EEXIST"))throw n}let e=de(t);if(!e.isDirectory()||e.uid!==process.getuid?.())throw new Error(`${t} is not a directory owned by this user`)}function me(t){return he(t,{recursive:!0,mode:448}),F(le(t)),le(t)}function X(t,e){F(T(t,"scratch"));let n=G(t,e);return F(n),n}function fe(t){let e=[];for(let n of Ie(t)){let o=/^srt-mux-(\d+)-[0-9a-z]+\.sock$/.exec(n);if(!(o===null||Ge(Number(o[1]))))try{ue(T(t,n)),e.push(n)}catch{}}return e}function Ge(t){if(t===process.pid)return!0;try{return process.kill(t,0),!0}catch(e){return E(e,"EPERM")}}function pe(t,e){let n=ce(e),o=I("sha256").update(n).digest("hex"),r=T(t,`srt-proxy-agent-${o}.jar`);if(!We(r)){let i=T(t,`.publish-${Ue()}.tmp`);Fe(i,n,{flag:"wx",mode:292});try{Be(i,r)}catch(a){if(!E(a,"EEXIST"))throw a}finally{ue(i)}}if(!de(r).isFile()||I("sha256").update(ce(r)).digest("hex")!==o)throw new Error(`${r} exists and is not the Java agent it is named for`);return{path:r,sha256:o}}import{createHash as ge}from"node:crypto";import{existsSync as z,lstatSync as J,opendirSync as Xe,realpathSync as ye,statSync as Je}from"node:fs";import{basename as xe,dirname as A,isAbsolute as be,join as g,relative as ze,resolve as S,sep as Ve}from"node:path";var Ye=/^(LD_.*|DYLD_.*|BASH_ENV|ENV|SHELLOPTS|BASHOPTS|BASH_FUNC_.*)$/;function ve(t){let e=Object.keys(t).filter(n=>Ye.test(n));if(e.length>0)throw new Error(`The command sandbox refuses an environment that sets ${e.join(", ")}: they run code before the sandbox holds`)}var V=128*1024;function k(t){let e=S(t),n=[];for(;!z(e)&&A(e)!==e;)n.unshift(xe(e)),e=A(e);return g(ye.native(e),...n)}var Ee=t=>({path:t,literal:!0}),Ze=/[*?[\]]/,Ke=["__GLOBSTAR_SLASH__","__GLOBSTAR__"];function Te(t){if(!be(t))throw new Error(`The command sandbox needs an absolute root, not ${t}`);let e=k(t);if(Ze.test(e)||Ke.some(n=>e.includes(n)))throw new Error(`The command sandbox refuses ${e} as a root: its name holds glob characters, which sandbox-runtime would read as a pattern`);return e}var Qe=[".gitconfig",".gitmodules",".bashrc",".bash_profile",".zshrc",".zprofile",".profile",".ripgreprc",".mcp.json",".vscode",".idea",".claude/commands",".claude/agents",".git/hooks",".git/config"];function qe(t,e){let n=[];for(let o of e)n.push(Ee(g(t,o)),`${t}/**/${o}`,`${t}/**/${o}/**`);return n}var en=["env","which","log","/bin/sh","/usr/bin/sandbox-exec"];function Ae(t){let e=t.split(":"),n=en.flatMap(o=>(o.startsWith("/")?[o]:e.map(r=>g(r,o))).filter(r=>z(r)).map(r=>ye.native(r)));return[...e,...n]}function Y(t,e,n){let o=(r,s)=>p(r,s)||p(s,r);for(let r of t){let s=e.find(i=>o(i,r));if(s!==void 0)throw new Error(`The command sandbox refuses to let commands write ${r}: it overlaps ${s}, which the sandbox runs before confinement`);if(o(n,r))throw new Error(`The command sandbox refuses to let commands write ${r}: it overlaps the temporary root`)}}var Z=(t,e)=>({spelling:t,canonical:e,foldsCase:tn(e)}),f=t=>Z(t,k(t)),nn=t=>t.replace(/[a-zA-Z]/g,e=>e===e.toLowerCase()?e.toUpperCase():e.toLowerCase());function tn(t,e=n=>J(n).dev){let n=S(t);for(;!z(n)&&A(n)!==n;)n=A(n);let o;try{Je(n).isDirectory()||(n=A(n)),o=e(n)}catch{return null}let r=(s,i)=>{let a=nn(i);if(a===i)return null;try{if(e(g(s,i))!==o)return null;let h=J(g(s,i)),c;try{c=J(g(s,a))}catch(l){return E(l,"ENOENT")?!1:null}return e(g(s,a))===o?h.ino===c.ino:null}catch{return null}};for(let s=n;;){let i=[];try{let c=Xe(s);try{for(let l=c.readSync();l!==null&&i.length<64;l=c.readSync())i.push(l.name)}finally{c.closeSync()}}catch{}for(let c of i){let l=r(s,c);if(l!==null)return l}let a=A(s);if(a===s)return null;try{if(e(a)!==o)return null}catch{return null}let h=r(a,xe(s));if(h!==null)return h;s=a}}function Se(t,e,n){return{writable:[e,...t.allowWrite,n],denyWrite:[...t.denyWrite,...t.conveniences,f("/tmp/claude"),f("/private/tmp/claude"),t.privateDirectory],denyRead:t.denyRead,protected:{root:e,suffixes:Qe}}}function ke(t){let e=n=>[...new Set(n.map(o=>o.canonical))].map(Ee);return{allowWrite:e(t.writable),denyWrite:[...e(t.denyWrite),...qe(t.protected.root.canonical,t.protected.suffixes)],denyRead:e(t.denyRead)}}var we={allowed:!0};function $e(t){let e=(r,s,i)=>{let a=s?r.normalize("NFC"):r;return i?a.toLowerCase():a},n=(r,s,i)=>{let a=i?s.foldsCase!==!1:s.foldsCase===!0,h=e(r,i,a);return p(h,e(S(s.spelling),i,a))||p(h,e(s.canonical,i,a))},o=r=>{let{root:s,suffixes:i}=t.protected,a=s.foldsCase!==!1;for(let h of[S(s.spelling),s.canonical]){let c={path:e(r,!0,a),base:e(h,!0,a)};if(!p(c.path,c.base))continue;let l=ze(c.base,c.path).split(Ve),m=i.find(x=>{let b=e(x,!0,a).split("/");return l.some((ee,w)=>b.every((_,u)=>l[w+u]===_))});if(m!==void 0)return m}return null};return{identity:ge("sha256").update(v(t.denyRead)).digest("hex"),check(r,s){if(!be(r))throw new Error(`The file policy judges absolute paths, not ${r}`);let i=S(r);if(s==="read"){let c=t.denyRead.find(l=>n(i,l,!0));return c===void 0?we:{allowed:!1,reason:`${r} is in ${c.spelling}, which this conversation may not read`}}if(!t.writable.some(c=>n(i,c,!1)))return{allowed:!1,reason:`${r} is outside the directories this conversation may write`};let a=t.denyWrite.find(c=>n(i,c,!0));if(a!==void 0)return{allowed:!1,reason:`${r} is in ${a.spelling}, which this conversation may not write`};let h=o(i);return h===null?we:{allowed:!1,reason:`${r} is protected (${h}) and cannot be written here`}}}}var K=['if [ -n "${LAGENT_SANDBOX_PATH+x}" ]; then PATH=$LAGENT_SANDBOX_PATH; export PATH; else unset PATH; fi',"TMPDIR=$LAGENT_SANDBOX_TMPDIR; export TMPDIR",'set -- "$LAGENT_SANDBOX_COMMAND"',"unset LAGENT_SANDBOX_PATH LAGENT_SANDBOX_TMPDIR LAGENT_SANDBOX_COMMAND",'exec /bin/sh -c "$1"'].join("; ");function _e(t){return"shell"in t?t.shell:`exec ${t.argv.map(e=>`'${e.replaceAll("'","'\\''")}'`).join(" ")}`}function De(t,e,n,o){let{PATH:r,...s}=t;return{...s,PATH:e,LAGENT_SANDBOX_TMPDIR:n,LAGENT_SANDBOX_COMMAND:o,...r===void 0?{}:{LAGENT_SANDBOX_PATH:r}}}var P="lagent-srt",rn="5",Re=t=>`${P}/${rn} sandbox-runtime/${t}`;function Ne(t){return`sha256:${ge("sha256").update(v({...t,bootstrap:K})).digest("hex")}`}function Oe(t){let e=t.filesystem.allowWrite.length===0?"":`, in ${t.filesystem.allowWrite.join(", ")}`,{network:n}=t,o=n.mode==="unrestricted"?"They have unrestricted network access.":n.allowedDomains.length===0?"They have no network access.":`They reach the network only through a proxy, and only these hosts: ${n.allowedDomains.join(", ")}; any other host is refused with HTTP 403. Tools that ignore HTTP_PROXY cannot connect. Refused requests are usually, not always, listed after the command's output.`;return[`run_command and start_command run in an OS sandbox. The file tools (read_file, list_dir, edit_files, rg_search, rg_files) are checked against the same rules, and name the rule when they refuse a path. Commands and edits can write only inside the conversation's working directory${e}, and in $TMPDIR. A command's refused write fails with "Operation not permitted", which is all it reports. Inside the working directory, git hooks, .git/config, shell startup files and editor settings cannot be written, by a command or by an edit.`,o,"A refusal is the sandbox, not a fault to work around: say what you needed instead."].join(" ")}import{z as Q}from"zod";var y=ae({name:"lagent:sandbox",authority:"conversation",initial:"confined",modes:["confined","unconfined"]});function C(t,e){if(t.journal.summary(e)===void 0)throw new Error(`Unknown thread: ${e}`);return y.recorded(t.journal,y.authorityOf(t,e))}var on=(t,e)=>y.recorded(t,e),sn=Q.strictObject({mode:Q.enum(["confined","unconfined"]),guidance:Q.string().nullable()}),an=re({plugin:"lagent:sandbox",kind:"notice",version:1,schema:sn}),cn="The sandbox is off: commands you start from now on run with the user's full permissions and network, and the file tools are no longer limited; commands already preparing or running keep the confinement they started with.",ln="Commands you start from now on are sandboxed, and the file tools follow the same rules; commands already preparing or running keep how they were launched.",q="The sandbox is unavailable on this host: commands, searches and file access are refused for this conversation.";function M(t,e){se(t,{event:an,agents:"all",rank:1,current:n=>{try{return e(n.threadId)}catch(o){if(o instanceof ie)return;throw o}},render:(n,o,{notices:r})=>{let s=oe(r)?.value??null;return n.mode==="unconfined"?(o??(s?.mode==="confined"?s:null))?.mode==="confined"?cn:null:n.guidance===null?null:n.guidance===q?n.guidance:`${ln} ${n.guidance}`}})}var j=new WeakMap;function L(t,e){let n=t.reflect.target,o=j.get(n);if(o!==void 0)throw new Error(`${e} cannot mount: ${o} already owns this application's sandbox, and the two exclude each other`);j.set(n,e),t.effect(()=>()=>{j.get(n)===e&&j.delete(n)})}var $=d.string().refine(U,{message:"Expected an absolute path"}),je=d.url({protocol:/^https?$/}).refine(t=>new URL(t).username===""&&new URL(t).password==="",{message:"A parent proxy URL carrying credentials is not supported"}),Me=d.strictObject({worker:d.union([d.instanceof(URL),d.string().min(1)]),searchPath:d.string().refine(t=>t.split(":").every(U),{message:"Every searchPath entry must be absolute"}),tmpRoot:$,javaAgentJar:$,unconfined:d.enum(["allowed","refused"]),filesystem:d.strictObject({allowWrite:d.array($),denyRead:d.array($),denyWrite:d.array($)}),network:d.discriminatedUnion("mode",[d.strictObject({mode:d.literal("allowlist"),allowedDomains:d.array(d.string().min(1)),deniedDomains:d.array(d.string().min(1)),parentProxy:d.union([d.literal("none"),d.strictObject({http:je.optional(),https:je.optional(),noProxy:d.string().optional()})])}),d.strictObject({mode:d.literal("unrestricted")})]),drainTimeoutMs:d.number().int().positive().default(2e4)});function un(t){if(t.mode==="unrestricted")return{deniedDomains:[]};let{parentProxy:e}=t;return{allowedDomains:t.allowedDomains,deniedDomains:t.deniedDomains,...e==="none"?{}:{parentProxy:e}}}function mn(t,e,n){return t==="allowlist"?e.isCopy?[]:[`sandbox-runtime does not advertise the Java agent copy ${n}`]:e.flags===0?[]:["sandbox-runtime advertises a Java agent although the network is unrestricted"]}var H=3e4,fn=15e3,pn=5e3,Ce=!1,W=class extends Pe{static inject=["confinement","journal","threads"];guidance;config;state="new";failure=null;admitting=!0;client=null;host=null;handle;releasing=null;constructor(e,n){super(e,"sandbox"),N(e,"sandbox"),this.config=Me.parse(n),this.guidance=Oe(this.config),L(e,"sandbox"),M(e,o=>{let r=this.mode(o);return{mode:r,guidance:r==="confined"?this.guidance:null}}),this.handle=e.confinement.handle(this),e.fiber.beforeUnload(()=>this.closeAndRelease())}async[Pe.init](){try{await this.start()}catch(e){this.state="failed",this.admitting=!1;let n=this.releaseHandle();throw this.announceFailure(),await n,await this.client?.terminate(),this.client=null,e}return()=>this.release()}get unconfined(){return this.config.unconfined}get health(){return this.state==="ready"&&this.admitting?"ready":"failed"}mode(e){return this.config.unconfined==="refused"?"confined":y.of(this.ctx,e)}async setMode(e,n){this.refuseGated(n);let o=this.ctx.journal.summary(e);if(o===void 0)throw new Error(`Unknown thread: ${e}`);if(o.origin!==null)throw new Error(`Thread ${e} is in its conversation's sandbox mode`);await y.set(this.ctx,e,n),this.handle.changed()}opening(e){return this.refuseGated(e),y.opening(e)}refuseGated(e){if(e==="unconfined"&&this.config.unconfined==="refused")throw new Error("This host refuses unconfined conversations: commands, searches and file access stay confined")}async snapshot(e){this.requireOpen();let n=this.ctx.journal.summary(e);if(n===void 0)throw new Error(`The sandbox cannot serve unknown thread ${e}`);if(this.mode(e)==="unconfined")return{files:null,prepare:s=>this.prepare(null,s)};let r={cwd:n.environment.cwd,scratch:B(e),policy:this.policyFor(n.environment.cwd,B(e),!1)};return{files:$e(r.policy),prepare:s=>this.prepare(r,s)}}async prepare(e,n){if(this.requireOpen(),!U(n.workdir))throw new Error(`The command sandbox needs an absolute workdir, not ${n.workdir}`);if(e===null)return this.plain(n);if(n.root!==e.cwd||n.scratch!==e.scratch)throw new Error(`The command sandbox refuses a launch whose root and scratch key differ from its operation's (${e.cwd}, ${e.scratch}): one operation has one policy`);return this.build(n,!1,e.policy)}plain(e){let n=this.host;if(n===null)throw new Error("The sandbox is not running");return{argv:R(e.program),cwd:e.workdir,env:e.environment,provenance:{launcher:P,version:n.version,policy:D},violations:async()=>null}}async start(){if(process.platform!=="darwin")throw new Error(`The command sandbox supports macOS only; on ${process.platform}, sandbox-runtime's launches sharing a directory can remove each other's protective mount points`);let e=k(this.config.tmpRoot),n=this.config.filesystem.denyWrite.map(f),o=n.find(u=>p(e,u.canonical)||p(u.canonical,e));if(o!==void 0)throw new Error(`tmpRoot must be outside filesystem.denyWrite, which would keep commands from writing their scratch directories: ${o.spelling}`);if(Ce)throw new Error("The command sandbox starts once per process: sandbox-runtime keeps process-wide state and is never restarted, so restart the host");Ce=!0,this.state="starting";let{config:r}=this,s=r.searchPath.split(":").map(k).join(":"),i=Ae(s),a=r.filesystem.allowWrite.map(f);Y(a.map(u=>u.canonical),i,e);let h=r.filesystem.denyRead.map(f),c=hn(),l=me(e);fe(l);let m=pe(l,r.javaAgentJar),x=new O(r.worker,{PATH:s,HOME:c,TMPDIR:l,CLAUDE_CODE_TMPDIR:l},u=>this.onWorkerDeath(u));this.client=x;let b=u=>({path:u,literal:!0}),ee={network:un(r.network),filesystem:{denyRead:h.map(u=>b(u.canonical)),allowWrite:[],denyWrite:n.map(u=>b(u.canonical))},javaAgentJarPath:m.path},w=await x.request({type:"init",config:ee,expect:{tmpdir:l,home:c}},{timeoutMs:H}),_=[...w.supported?[]:["this platform is not supported"],...w.errors,...w.warnings,...mn(r.network.mode,w.javaAgent,m.path)];if(_.length>0)throw new Error(`The command sandbox cannot start here: ${_.join("; ")}`);if(this.host={tmpRoot:e,cwd:process.cwd(),home:c,searchPath:s,trusted:i,extraRoots:r.filesystem.allowWrite,facts:{allowWrite:a,denyWrite:n,denyRead:h,conveniences:w.conveniences.map(f),privateDirectory:f(l)},version:Re(w.version),javaAgentSha256:m.sha256},await this.selfCheck(e),!x.alive||this.failure!==null)throw new Error(`The command sandbox worker ended while it started${this.failure===null?"":`: ${this.failure.message}`}`);this.state="ready"}async selfCheck(e){let n=X(e,"self-check"),o=await this.build({program:{shell:"exit 0"},workdir:n,root:n,scratch:"self-check",environment:{},signal:AbortSignal.timeout(H)},!0),r=await ne({command:o.argv,cwd:o.cwd,env:o.env,signal:AbortSignal.timeout(H),timeoutMs:H});if(r.exitCode!==0){let s=r.exitSignal===null?`exit code ${r.exitCode}`:`signal ${r.exitSignal}`;throw new Error(`The command sandbox could not run a confined command: ${r.output.trim()||s}`)}}requireOpen(){if(this.state==="failed")throw new Error(`The sandbox failed${this.failure===null?"":`: ${this.failure.message}`}. Nothing runs unconfined; it stays failed until lagent restarts.`);if(this.state==="new"||this.state==="starting")throw new Error("The sandbox is still starting");if(this.state!=="ready"||!this.admitting)throw new Error("The sandbox is shutting down")}async build(e,n,o){let r=this.host,s=this.client;if(r===null||s===null)throw new Error("The sandbox is not running");if(ve(e.environment),!U(e.workdir))throw new Error(`The command sandbox needs an absolute workdir, not ${e.workdir}`);let i=_e(e.program);if(Buffer.byteLength(i)>V)throw new Error(`The command sandbox refuses a command longer than ${V} bytes`);if(process.cwd()!==r.cwd)throw new Error(`The host changed its working directory from ${r.cwd} to ${process.cwd()}: the command sandbox refuses to launch`);let a=this.policyFor(e.root,e.scratch,n);if(o!==void 0&&v(o)!==v(a))throw new Error("The command sandbox refuses to launch: a writable root or a link in it now leads somewhere other than where this operation selected its policy, so it could not enforce the same one. Start the operation again.");let h=o??a,c=X(r.tmpRoot,e.scratch),l=ke(h),m=dn();return{argv:["/bin/sh","-c",`exec ${await s.request({type:"wrap",command:K,commandId:m,commandText:i.slice(0,1e3),config:{filesystem:l}},{timeoutMs:fn,signal:e.signal})}`],cwd:e.workdir,env:De(e.environment,r.searchPath,c,i),provenance:{launcher:P,version:r.version,policy:Ne({filesystem:l,network:this.config.network,searchPath:r.searchPath,cwd:r.cwd,home:r.home,conveniences:r.facts.conveniences.map(b=>b.spelling),javaAgent:r.javaAgentSha256,version:r.version})},violations:()=>this.violationsOf(m)}}policyFor(e,n,o){let r=this.host;if(r===null)throw new Error("The sandbox is not running");let s=Z(e,Te(e)),i=r.extraRoots.map(f),a=i.map(h=>h.canonical);return Y(o?a:[s.canonical,...a],r.trusted,r.tmpRoot),Se({...r.facts,allowWrite:i},s,f(G(r.tmpRoot,n)))}async violationsOf(e){if(this.client===null)return null;let n=await this.client.request({type:"violations",commandId:e},{timeoutMs:te});return n.length===0?null:`<sandbox_violations>
|
|
2
|
+
${n.join(`
|
|
3
|
+
`)}
|
|
4
|
+
</sandbox_violations>`}onWorkerDeath(e){this.failure??=e,this.state==="ready"&&(this.state="failed",this.admitting=!1,this.ctx.reportError(e),this.releaseHandle(),this.announceFailure())}announceFailure(){this.ctx.parallel("lagent/sandbox/health","failed").catch(e=>this.ctx.reportError(e))}async closeAndRelease(){this.admitting=!1,this.state==="ready"&&(this.state="stopping"),await this.releaseHandle()}releaseHandle(){return this.releasing??=this.handle.release(this.config.drainTimeoutMs).then(e=>(e||this.ctx.reportError(new Error(`A command could not be confirmed stopped within ${this.config.drainTimeoutMs} ms: a confined process may still run, so the sandbox worker is kept until the process exits`)),e)),this.releasing}async release(){let e=this.client;if(e!==null){if(!await this.releaseHandle()){e.unref();return}e.alive&&await e.request({type:"reset"},{timeoutMs:pn}).catch(()=>{}),await e.terminate(),this.client=null}}};import{z as Le}from"zod";var wn=Le.strictObject({drainTimeoutMs:Le.number().int().positive()}),gn="lagent-sandbox-absent",yn="1",xn={name:"lagent:sandbox-absent",inject:["journal","threads","confinement"],apply(t,e){N(t,"sandboxAbsent");let n=wn.parse(e);L(t,"sandboxAbsent");let o=t.confinement.handle({async snapshot(s){if(C(t,s)==="confined")throw new Error("This conversation is sandboxed, and the host started without a sandbox, so its commands, searches and file access are refused.");return{files:null,async prepare(i){return{argv:R(i.program),cwd:i.workdir,env:i.environment,provenance:{launcher:gn,version:yn,policy:D},violations:async()=>null}}}}}),r=null;t.fiber.beforeUnload(()=>(r??=o.release(n.drainTimeoutMs).then(s=>(s||t.reportError(new Error(`A command could not be confirmed stopped within ${n.drainTimeoutMs} ms`)),s)),r.then(()=>{}))),M(t,s=>C(t,s)==="confined"?{mode:"confined",guidance:q}:{mode:"unconfined",guidance:null})}};var dt=W,ht=new URL("./worker.js",import.meta.url);function ut(){let t=bn(import.meta.url).resolve("@anthropic-ai/sandbox-runtime/package.json");return En(vn(t),"vendor","java-proxy-agent","srt-proxy-agent.jar")}export{Me as SandboxConfigSchema,dt as sandbox,xn as sandboxAbsent,C as sandboxRecorded,on as sandboxRecordedOn,ht as sandboxWorker,ut as srtJavaAgentJar};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Context } from '@leuduan/cordis';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import type { PluginJournal } from '
|
|
3
|
+
import type { PluginJournal } from '../../journal/index.js';
|
|
4
4
|
/**
|
|
5
5
|
* Whether a conversation's shell commands run in the OS sandbox. One vocabulary from lagent to a
|
|
6
6
|
* host's wire; "sandboxed" and "unsandboxed" are a display's words.
|
|
@@ -11,7 +11,7 @@ export type SandboxMode = 'confined' | 'unconfined';
|
|
|
11
11
|
* mode, so a sub-agent is never in a mode its conversation is not. Private to this package: the
|
|
12
12
|
* service is its only writer, and `sandboxRecorded` is how a host reads it.
|
|
13
13
|
*/
|
|
14
|
-
export declare const sandboxModes: import("
|
|
14
|
+
export declare const sandboxModes: import("../../services/modes.js").Modes<SandboxMode>;
|
|
15
15
|
/**
|
|
16
16
|
* The mode recorded for the conversation `threadId` belongs to, `null` only when none was ever
|
|
17
17
|
* recorded: never the `confined` a thread without a row reads as. For a host deciding what the
|
|
@@ -37,10 +37,10 @@ declare const SandboxFactSchema: z.ZodObject<{
|
|
|
37
37
|
}, z.core.$strict>;
|
|
38
38
|
export type SandboxFact = z.output<typeof SandboxFactSchema>;
|
|
39
39
|
/**
|
|
40
|
-
* What a confined conversation is told on a host started without a sandbox, where
|
|
41
|
-
*
|
|
40
|
+
* What a confined conversation is told on a host started without a sandbox, where its commands, searches
|
|
41
|
+
* and file access are refused.
|
|
42
42
|
*/
|
|
43
|
-
export declare const ABSENT_GUIDANCE = "The sandbox is unavailable on this host:
|
|
43
|
+
export declare const ABSENT_GUIDANCE = "The sandbox is unavailable on this host: commands, searches and file access are refused for this conversation.";
|
|
44
44
|
/**
|
|
45
45
|
* Tells every agent's threads the fact `current` says at their model steps. The guidance is part of
|
|
46
46
|
* the compared fact, so a changed sandbox file, or a host that lost its sandbox, is told again. A
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import type { FilePolicy, LaunchProgram } from '../../services/confinement/service.js';
|
|
2
|
+
import type { SpawnEnvironment } from '../../sys/spawn.js';
|
|
3
|
+
import type { CallConfig, PathEntry } from './protocol.js';
|
|
4
|
+
export declare function refuseStartupInjection(environment: SpawnEnvironment): void;
|
|
5
|
+
/** The most command text one launch carries, which travels in the environment of the process that starts it. */
|
|
6
|
+
export declare const MAX_COMMAND_BYTES: number;
|
|
7
|
+
/**
|
|
8
|
+
* `path` with every link in its existing part resolved, so containment is judged on real locations.
|
|
9
|
+
* The one rule for every path of a policy: a link that leads nowhere is not followed, it stays the
|
|
10
|
+
* name it is, so an entry that is a dangling link covers its own name and nothing it points at.
|
|
11
|
+
*/
|
|
12
|
+
export declare function canonical(path: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* The root a command's protected names are rebuilt for, canonical. sandbox-runtime substitutes the
|
|
15
|
+
* real prefix before it compiles a pattern, and cannot anchor one this module hands it, so a root
|
|
16
|
+
* whose real path holds glob characters or its compiler's placeholders would turn the patterns
|
|
17
|
+
* below into something else: it is refused, not escaped.
|
|
18
|
+
*/
|
|
19
|
+
export declare function canonicalRoot(root: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* The names sandbox-runtime protects below its working directory, which here is the host's, not the thread's:
|
|
22
|
+
* relative suffixes, matched at the root and at any depth below it, with everything beneath them.
|
|
23
|
+
*/
|
|
24
|
+
export declare const PROTECTED_SUFFIXES: readonly string[];
|
|
25
|
+
/**
|
|
26
|
+
* sandbox-runtime's mandatory write denies, rebuilt for `root`: its own set anchors at the host's
|
|
27
|
+
* working directory and only ever adds denies there. Each of `suffixes` once as the path at the root and
|
|
28
|
+
* twice as patterns for the same name anywhere beneath it, itself and its descendants. `root` is
|
|
29
|
+
* canonical and holds no glob characters (`canonicalRoot`).
|
|
30
|
+
*/
|
|
31
|
+
export declare function mandatoryDenies(root: string, suffixes: readonly string[]): PathEntry[];
|
|
32
|
+
/**
|
|
33
|
+
* The programs a launch runs before confinement, as the search path finds them: the directories
|
|
34
|
+
* searched, since a program dropped into an earlier one would win, and the real file of every
|
|
35
|
+
* candidate for each program, wherever its link leads.
|
|
36
|
+
*/
|
|
37
|
+
export declare function helperLocations(searchPath: string): string[];
|
|
38
|
+
/**
|
|
39
|
+
* Refuses roots a command could use against the sandbox itself: one overlapping, either way, what
|
|
40
|
+
* a launch runs before confinement, or the temporary root, whose directories the host creates
|
|
41
|
+
* unconfined. `roots` are canonical.
|
|
42
|
+
*/
|
|
43
|
+
export declare function refuseUnsafeRoots(roots: readonly string[], trusted: readonly string[], tmpRoot: string): void;
|
|
44
|
+
/**
|
|
45
|
+
* A path as the host configured it and as it really is, with whether its volume folds case
|
|
46
|
+
* (`null`: it could not be told). Both spellings are matched when a path is judged by name
|
|
47
|
+
* (`filePolicy`), so a workspace configured by its link path still matches a path spelled by its
|
|
48
|
+
* real one, and the other way round; the OS sees only the real one (`srtFilesystem`).
|
|
49
|
+
*/
|
|
50
|
+
export type Anchor = {
|
|
51
|
+
readonly spelling: string;
|
|
52
|
+
readonly canonical: string;
|
|
53
|
+
readonly foldsCase: boolean | null;
|
|
54
|
+
};
|
|
55
|
+
/** `spelling` with the real path it leads to, which the caller already has, and its volume's case rule. */
|
|
56
|
+
export declare const anchorAt: (spelling: string, canonicalPath: string) => Anchor;
|
|
57
|
+
/** `path` as configured, with where it really leads. */
|
|
58
|
+
export declare const anchor: (path: string) => Anchor;
|
|
59
|
+
/**
|
|
60
|
+
* Whether the volume that holds `path` treats names that differ only in case as one, or `null` when
|
|
61
|
+
* it cannot be told. `path` may not exist yet: the answer is the volume of its nearest existing
|
|
62
|
+
* directory, probed by looking one of its entries up in another case and comparing what the two
|
|
63
|
+
* lookups find (device and inode), never by assuming a platform's default. Only what lives on that
|
|
64
|
+
* directory's own volume is probed: an entry on another device (a mount), or the directory's own
|
|
65
|
+
* name in a parent on another device, would answer for another volume. A directory with no entry to
|
|
66
|
+
* probe is probed through its own name in its parent, and so up, on the same device. `deviceOf` is
|
|
67
|
+
* the device lookup, replaceable to simulate volumes in tests.
|
|
68
|
+
*/
|
|
69
|
+
export declare function foldsCase(path: string, deviceOf?: (path: string) => number): boolean | null;
|
|
70
|
+
/**
|
|
71
|
+
* What the sandbox allows a thread's processes and file tools, as one value in a restricted
|
|
72
|
+
* language: literal subtrees, each a path and everything below it, and protected relative suffixes
|
|
73
|
+
* under one root. `srtFilesystem` renders it for the OS, `filePolicy` for the file tools in this process.
|
|
74
|
+
*/
|
|
75
|
+
export type FilesystemPolicy = {
|
|
76
|
+
/** Writable subtrees: the thread's root, the host's roots and the scratch directory. */
|
|
77
|
+
readonly writable: readonly Anchor[];
|
|
78
|
+
/** Unwritable even inside a writable subtree: what the host and the sandbox keep for themselves. */
|
|
79
|
+
readonly denyWrite: readonly Anchor[];
|
|
80
|
+
/** Unreadable subtrees: credentials. */
|
|
81
|
+
readonly denyRead: readonly Anchor[];
|
|
82
|
+
/** Names unwritable at `root` and at any depth below it, with everything beneath them. */
|
|
83
|
+
readonly protected: {
|
|
84
|
+
readonly root: Anchor;
|
|
85
|
+
readonly suffixes: readonly string[];
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
/** What a thread's filesystem policy is made of, besides its root and scratch directory. */
|
|
89
|
+
export type HostFacts = {
|
|
90
|
+
readonly allowWrite: readonly Anchor[];
|
|
91
|
+
readonly denyWrite: readonly Anchor[];
|
|
92
|
+
readonly denyRead: readonly Anchor[];
|
|
93
|
+
/** The paths under the home directory sandbox-runtime keeps writable for itself, as its worker reported them. */
|
|
94
|
+
readonly conveniences: readonly Anchor[];
|
|
95
|
+
readonly privateDirectory: Anchor;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* A thread's filesystem policy: writes only in the root, the host's roots and the scratch directory,
|
|
99
|
+
* and never in what the host or sandbox-runtime keeps for itself or in the root's protected names.
|
|
100
|
+
* `root` is the thread's working directory, `canonicalRoot`'s answer for its spelling.
|
|
101
|
+
*/
|
|
102
|
+
export declare function filesystemPolicy(facts: HostFacts, root: Anchor, scratchDirectory: Anchor): FilesystemPolicy;
|
|
103
|
+
/** The OS's lists for the policy, which replace the initialized ones for the call: every path real and literal. */
|
|
104
|
+
export declare function srtFilesystem(policy: FilesystemPolicy): CallConfig['filesystem'];
|
|
105
|
+
/**
|
|
106
|
+
* The policy for file tools in this process: the same value the OS enforces on processes, judged by
|
|
107
|
+
* name. It reads no file system, so a link in a path is the caller's to resolve: it asks for the
|
|
108
|
+
* path as requested and for where it resolves, and both must pass. A path is in a subtree, or under
|
|
109
|
+
* the protected root, by either spelling of that entry.
|
|
110
|
+
*
|
|
111
|
+
* Names are compared as the entry's volume would see them, and doubt never widens what is allowed.
|
|
112
|
+
* A rule that refuses compares in Unicode normalization form NFC, since a volume such as APFS treats
|
|
113
|
+
* the composed and decomposed spellings of a name as one, and folds case where the volume does:
|
|
114
|
+
* `.SSH` is `.ssh` there, even when `.ssh` does not exist yet, and `.GITCONFIG` is another file on a
|
|
115
|
+
* volume that keeps case. Where that could not be told (`foldsCase: null`) it folds. A rule that
|
|
116
|
+
* allows compares the spellings as they are, folding case only where the volume is known to. So on
|
|
117
|
+
* a volume that tells normalizations apart a refusal can over-match, and on APFS a writable root
|
|
118
|
+
* typed in another normalization than it was configured in is refused, never allowed too widely.
|
|
119
|
+
*/
|
|
120
|
+
export declare function filePolicy(policy: FilesystemPolicy): FilePolicy;
|
|
121
|
+
/**
|
|
122
|
+
* The script the sandbox runs: restores the command's `PATH`, sets its temporary directory and runs
|
|
123
|
+
* it with `/bin/sh -c`. Everything it needs arrives in variables, never spliced into the script.
|
|
124
|
+
*/
|
|
125
|
+
export declare const BOOTSTRAP: string;
|
|
126
|
+
/**
|
|
127
|
+
* A program as the shell text the sandbox runs: shell text as it is, an argv as `exec` and every word
|
|
128
|
+
* single-quoted, so the shell splits it back into exactly those words and looks the program up as a
|
|
129
|
+
* plain spawn does, never as one of its builtins or functions.
|
|
130
|
+
*/
|
|
131
|
+
export declare function programText(program: LaunchProgram): string;
|
|
132
|
+
/**
|
|
133
|
+
* The environment of the process that starts the sandbox: the command's own, but with the trusted
|
|
134
|
+
* `PATH` until the sandbox holds, since the wrapped line runs `env` and `sandbox-exec` before
|
|
135
|
+
* confinement. The bootstrap restores the rest.
|
|
136
|
+
*/
|
|
137
|
+
export declare function launchEnvironment(environment: SpawnEnvironment, searchPath: string, scratchDirectory: string, command: string): SpawnEnvironment;
|
|
138
|
+
/** What the name and version say in a command's recorded launch. */
|
|
139
|
+
export declare const LAUNCHER = "lagent-srt";
|
|
140
|
+
export declare const versionOf: (runtime: string) => string;
|
|
141
|
+
/** Everything enforcement depends on, digested: recorded with a launch as its policy. */
|
|
142
|
+
export declare function policyDigest(facts: {
|
|
143
|
+
readonly filesystem: CallConfig['filesystem'];
|
|
144
|
+
readonly network: unknown;
|
|
145
|
+
readonly searchPath: string;
|
|
146
|
+
readonly cwd: string;
|
|
147
|
+
readonly home: string;
|
|
148
|
+
readonly conveniences: readonly string[];
|
|
149
|
+
readonly javaAgent: string;
|
|
150
|
+
readonly version: string;
|
|
151
|
+
}): string;
|
|
152
|
+
/** What the model is told about sandboxed commands. */
|
|
153
|
+
export declare function instructionsFor(config: {
|
|
154
|
+
readonly filesystem: {
|
|
155
|
+
readonly allowWrite: readonly string[];
|
|
156
|
+
};
|
|
157
|
+
readonly network: {
|
|
158
|
+
readonly mode: 'allowlist';
|
|
159
|
+
readonly allowedDomains: readonly string[];
|
|
160
|
+
} | {
|
|
161
|
+
readonly mode: 'unrestricted';
|
|
162
|
+
};
|
|
163
|
+
}): string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Service, type Context } from '@leuduan/cordis';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import type { WriteRows } from '
|
|
4
|
-
import { type
|
|
3
|
+
import type { WriteRows } from '../../state/index.js';
|
|
4
|
+
import { type ConfinementHandler, type ConfinementSnapshot } from '../../services/confinement/service.js';
|
|
5
5
|
import { type SandboxMode } from './mode.js';
|
|
6
6
|
export declare const SandboxConfigSchema: z.ZodObject<{
|
|
7
7
|
worker: z.ZodUnion<readonly [z.ZodCustom<import("url").URL, import("url").URL>, z.ZodString]>;
|
|
@@ -36,7 +36,7 @@ export type SandboxConfigInput = z.input<typeof SandboxConfigSchema>;
|
|
|
36
36
|
export type SandboxHealth = 'ready' | 'failed';
|
|
37
37
|
declare module '@leuduan/cordis' {
|
|
38
38
|
interface Context {
|
|
39
|
-
/** The OS sandbox for shell commands, the
|
|
39
|
+
/** The OS sandbox for shell commands, the confinement handler of `ctx.confinement` (optional plugin, `@leuduan/lagent/plugins/sandbox`). */
|
|
40
40
|
sandbox: SandboxService;
|
|
41
41
|
}
|
|
42
42
|
interface Events {
|
|
@@ -48,30 +48,29 @@ declare module '@leuduan/cordis' {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
|
-
* The OS sandbox for shell commands, as a service and as the one
|
|
51
|
+
* The OS sandbox for shell commands, as a service and as the one confinement handler of `ctx.confinement`.
|
|
52
52
|
* sandbox-runtime runs on a worker thread of this process (`./worker.ts`), started once, and this
|
|
53
53
|
* service owns it: the policy built for each command, the worker's messages and deadlines, and the
|
|
54
54
|
* order of teardown.
|
|
55
55
|
*
|
|
56
56
|
* Each conversation is confined or unconfined, as its durable mode says (`mode`, `setMode`, `opening`),
|
|
57
|
-
* unless the host's `unconfined: 'refused'` confines them all. The mode is read once
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* handler's, so it stops with it and a failed sandbox refuses it too. The model is
|
|
61
|
-
* notice, never in the prompt.
|
|
57
|
+
* unless the host's `unconfined: 'refused'` confines them all. The mode is read once, in the snapshot
|
|
58
|
+
* an operation takes at its first use: a switch applies to operations not yet used, and one already
|
|
59
|
+
* used keeps the policy it selected, its file access and launches included. An unconfined launch is
|
|
60
|
+
* still the sandbox's handler's, so it stops with it and a failed sandbox refuses it too. The model is
|
|
61
|
+
* told the mode as a notice, never in the prompt.
|
|
62
62
|
*
|
|
63
63
|
* The handler is registered before the worker starts and refuses every launch until the self-check
|
|
64
64
|
* passed, so a sandbox that fails to start never leaves a window in which launches run plain.
|
|
65
65
|
* Teardown releases the handle first, so what it confined ends before its proxy does. sandbox-runtime
|
|
66
66
|
* starts at most once per process, so a failed or disposed service is never restarted. The teardown
|
|
67
|
-
* order and its failure cases are in `docs/sandbox.md`.
|
|
67
|
+
* order and its failure cases are in `docs/records/sandbox.md`.
|
|
68
68
|
*/
|
|
69
|
-
export declare class SandboxService extends Service<SandboxConfigInput> implements
|
|
69
|
+
export declare class SandboxService extends Service<SandboxConfigInput> implements ConfinementHandler {
|
|
70
70
|
static inject: string[];
|
|
71
71
|
/** What the model is told about confined commands: part of the notice's fact, so a changed one is told again. */
|
|
72
72
|
private readonly guidance;
|
|
73
73
|
private readonly config;
|
|
74
|
-
private readonly dataDir;
|
|
75
74
|
private state;
|
|
76
75
|
private failure;
|
|
77
76
|
private admitting;
|
|
@@ -104,7 +103,12 @@ export declare class SandboxService extends Service<SandboxConfigInput> implemen
|
|
|
104
103
|
*/
|
|
105
104
|
opening(mode: SandboxMode): WriteRows;
|
|
106
105
|
private refuseGated;
|
|
107
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Admits one operation for `threadId` and reads its mode, once: the operation's launches and its
|
|
108
|
+
* file policy keep the mode selected here, whatever switch lands while they prepare or run.
|
|
109
|
+
*/
|
|
110
|
+
snapshot(threadId: string): Promise<ConfinementSnapshot>;
|
|
111
|
+
private prepare;
|
|
108
112
|
/**
|
|
109
113
|
* An unconfined launch: what core would have run with no handler, with the provenance that says so.
|
|
110
114
|
* None of the confined machinery runs: no scratch directory, policy, digest, worker request or
|
|
@@ -120,6 +124,12 @@ export declare class SandboxService extends Service<SandboxConfigInput> implemen
|
|
|
120
124
|
* `trusted` skips the root checks, for the one launch whose root lies in the temporary root.
|
|
121
125
|
*/
|
|
122
126
|
private build;
|
|
127
|
+
/**
|
|
128
|
+
* The filesystem policy for a root and a scratch key, the one value both a launch's confinement
|
|
129
|
+
* and the file tools' policy are rendered from. `trusted` skips the root's own checks, for the
|
|
130
|
+
* one launch whose root lies in the temporary root.
|
|
131
|
+
*/
|
|
132
|
+
private policyFor;
|
|
123
133
|
/** What sandbox-runtime recorded against one command: best-effort, from its global tail of recent events. */
|
|
124
134
|
private violationsOf;
|
|
125
135
|
private onWorkerDeath;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{a as I}from"../../chunks/C5LYCXJG.js";import{a as H,d as B}from"../../chunks/J5S4URAW.js";import{a as Q}from"../../chunks/SL2YLB2F.js";import{b as Z}from"../../chunks/RK3VP2Q4.js";import"../../chunks/74HKUGRR.js";import{a as G}from"../../chunks/QMMLQJBS.js";import{a as L}from"../../chunks/YIR5MYGU.js";import{a as y,b as z}from"../../chunks/NAOUTU7A.js";import"../../chunks/UKXMCHHT.js";import{b as T}from"../../chunks/DRYAN665.js";import{d as R}from"../../chunks/2OSESBIL.js";import{G as P}from"../../chunks/YLNJXQ3Z.js";import"../../chunks/DDANMY45.js";import"../../chunks/37YOGDGY.js";import"../../chunks/KESCTXGO.js";import"../../chunks/TYTQRLTX.js";import"../../chunks/D7E6EWIE.js";import{Service as he}from"@leuduan/cordis";import{z as A}from"zod";import{z as c}from"zod";var v=c.number().int().nonnegative(),ae=c.discriminatedUnion("op",[c.strictObject({op:c.literal("created"),id:c.string().min(1),due_at:v,prompt:c.string().min(1),reason:c.string().min(1),time_zone:c.string().min(1),created_at:v,call_seq:c.number().int().positive().nullable()}),c.strictObject({op:c.literal("fired"),id:c.string().min(1),input_id:c.string().min(1)}),c.strictObject({op:c.literal("cancelled"),id:c.string().min(1),reason:c.string()})]),g=T({plugin:"lagent:schedules",kind:"schedule",version:1,schema:ae}),w=n=>n.cancelled?"cancelled":n.delivered===null?"pending":"fired",h=g.projection({key:"schedules",onFork:"reset",init:()=>new Map,apply(n,e){if(e.op==="created")return n.has(e.id)||n.set(e.id,{created:e,delivered:null,cancelled:!1}),n;let t=n.get(e.id);return t===void 0||n.set(e.id,e.op==="fired"?{...t,delivered:e.input_id}:{...t,cancelled:!0}),n}}),j=Q({key:"lagent:schedules",version:1,schema:c.strictObject({next_wake_at:v.nullable()}),applies:()=>!0,compute(n,e){let t=null;for(let r of n.state(h,e).values())w(r)==="pending"&&(t=t===null?r.created.due_at:Math.min(t,r.created.due_at));return{next_wake_at:t}}});function O(n,e){let t=j.read(n,e);return t.status==="available"?t.value.next_wake_at:null}var ce=c.strictObject({schedule_id:c.string().min(1),due_at:v,summary:c.string()}),$=T({plugin:"lagent:schedules",kind:"wake",version:1,schema:ce}),ue=n=>$.readSource(n);function J(n){try{return new Intl.DateTimeFormat("en-US",{timeZone:n}),!0}catch{return!1}}var Y=new Map;function le(n){let e=Y.get(n);return e===void 0&&(e=new Intl.DateTimeFormat("en-US",{timeZone:n,hourCycle:"h23",year:"numeric",month:"numeric",day:"numeric",hour:"numeric",minute:"numeric",second:"numeric"}),Y.set(n,e)),e}function M(n,e){let t={};for(let r of le(e).formatToParts(n))r.type!=="literal"&&(t[r.type]=Number(r.value));return{year:t.year,month:t.month,day:t.day,hour:t.hour,minute:t.minute,second:t.second}}function U(n,e){let t=M(n,e);return Date.UTC(t.year,t.month-1,t.day,t.hour,t.minute,t.second)-Math.floor(n/1e3)*1e3}var K=(n,e)=>n.year===e.year&&n.month===e.month&&n.day===e.day&&n.hour===e.hour&&n.minute===e.minute;function de(n,e){let t=Date.UTC(n.year,n.month-1,n.day,n.hour,n.minute),r=U(t-54e6,e),s=U(t+54e6,e),u=[...new Set([t-r,t-s])].sort((a,l)=>a-l);for(let a of u)if(K(M(a,e),n))return a;let o=Math.min(t-r,t-s),i=Math.max(t-r,t-s);for(;i-o>1e3;){let a=Math.floor((o+i)/2e3)*1e3;U(a,e)===s?i=a:o=a}return i}function V(n,e){let t=/^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2}))?(?:(Z)|([+-])(\d{2}):?(\d{2}))?$/.exec(n.trim());if(t===null)return null;let[r,s,u,o,i,a]=t.slice(1,7).map(_=>Number(_??0)),l=new Date(Date.UTC(r,s-1,u));if(l.getUTCFullYear()!==r||l.getUTCMonth()!==s-1||l.getUTCDate()!==u||o>23||i>59||a>59)return null;let[,,,,,,,d,p,S,ie]=t;if(d!==void 0||p!==void 0){let _=Number(S??0),q=Number(ie??0);if(_>14||q>59)return null;let oe=(p==="-"?-1:1)*(_*60+q)*6e4;return Date.UTC(r,s-1,u,o,i,a)-oe}let C={year:r,month:s,day:u,hour:o,minute:i},b=de(C,e);return K(M(b,e),C)?b+a*1e3:b}var X=A.number().int().positive(),me=A.strictObject({clock:H,timeZone:A.string().refine(J,"timeZone must be an IANA time zone"),maxDelayMs:X,maxPendingPerThread:X}),m=class extends Error{constructor(e){super(e),this.name="ScheduleRefusedError"}},D="Scheduled wakes are for conversations a person talks to; an agent working for another agent cannot schedule one.",fe="A scheduled wake can be created only in a turn a person started, not from a wake or a notice. Tell the person what to schedule, or ask them.",pe=n=>`sch-${n}`,ee=n=>`wake_${n}`,W=n=>n.replaceAll("&","&").replaceAll('"',""").replaceAll("<","<"),k=n=>new Date(n).toISOString().replace(".000Z","Z"),te=(n,e)=>`A scheduled wake may be due at most ${Math.round(n/6e4)} minutes from now, by ${k(e+n)}.`;function E(n,e,t){let{created:r}=e,s=e.delivered!==null&&!e.cancelled&&t.input(e.delivered).state==="queued";return{id:r.id,threadId:n,dueAt:r.due_at,timeZone:r.time_zone,prompt:r.prompt,reason:r.reason,status:w(e),unread:s,createdAt:r.created_at}}var x=class extends he{static inject=["threads","journal"];config;queue;inFlight=new Set;refreshed=Promise.resolve([]);armed=!1;retries;closed=!1;constructor(e,t){super(e,"schedules"),this.config=me.parse(t),this.queue=B(this.config.clock,r=>e.reportError(r)),this.retries=I(this.config.clock,r=>e.reportError(r)),e.effect(()=>e.journal.register(h)),e.effect(()=>e.journal.subscribe(r=>{this.armed&&g.read(r)!==void 0&&this.arm(r.thread_id)})),this.refreshed=j.register(e).refreshed,e.effect(()=>{let r=e.threads.registerOwner({owner:"lagent:schedules",repair:()=>this.catchUp()});return()=>r.dispose()}),L(e,()=>this.shutdown())}list({threadId:e}){let t=this.ctx.threads.view(e);return[...this.ctx.journal.state(h,e).values()].map(r=>E(e,r,t))}async create(e,t,r={}){let s=this.thread(e),u=r.callSeq===void 0?P("sch"):pe(r.callSeq),o=await s.read(l=>{let d=this.ctx.journal.state(h,e).get(u);if(d!==void 0)return E(e,d,l);if(s.origin!=null)throw new m(D);if(r.requirePersonTurn===!0&&!this.ctx.threads.chain(e).personTurn)throw new m(fe)});if(o!==void 0)return o;let i=this.createdRow(u,t,r.callSeq??null),{value:a}=await this.ctx.journal.transact(e,()=>{let l=this.ctx.journal.state(h,e);if(l.has(u))return{rows:[],value:null};let d=[...l.values()].filter(S=>w(S)==="pending").length;if(d>=this.config.maxPendingPerThread)return{rows:[],value:`This conversation already has ${d} scheduled wakes waiting, the most it may; cancel one with cancel_wake first.`};let p=this.config.clock.now();return i.due_at<=p?{rows:[],value:`${k(i.due_at)} has already passed; it is now ${k(p)}.`}:i.due_at>p+this.config.maxDelayMs?{rows:[],value:te(this.config.maxDelayMs,p)}:{rows:[g.entry({...i,created_at:p},{threadId:e})],value:null}});if(a!==null)throw new m(a);return s.read(l=>E(e,this.ctx.journal.state(h,e).get(u),l))}cancel(e,t,r="cancelled"){return this.track(async()=>{let s=this.thread(e);if(!await s.read(()=>this.ctx.journal.state(h,e).has(t)))throw new m(`No scheduled wake ${t} in this conversation.`);let o=await s.withdraw(ee(t),{rows:()=>this.ctx.journal.state(h,e).get(t).cancelled?[]:[g.entry({op:"cancelled",id:t,reason:r},{threadId:e})]});if(o.status==="refused")throw new Error(o.refusal.message);return{withdrawn:o.status==="withdrawn"}})}cancelRows(e,t="the conversation was stopped"){return[...this.ctx.journal.state(h,e).values()].filter(r=>w(r)==="pending").map(r=>g.entry({op:"cancelled",id:r.created.id,reason:t},{threadId:e}))}thread(e){let t=this.ctx.threads.get(e);if(t===void 0)throw new m(`No thread ${e}`);return t}createdRow(e,t,r){let{clock:s,timeZone:u,maxDelayMs:o}=this.config,i=s.now(),a=p=>{throw new m(p)};t.prompt.trim()===""&&a("A scheduled wake needs a prompt."),t.reason.trim()===""&&a("A scheduled wake needs a reason.");let{when:l}=t,d;return"in_seconds"in l?((!Number.isInteger(l.in_seconds)||l.in_seconds<1)&&a("in_seconds must be a whole number of at least 1."),d=i+l.in_seconds*1e3):(d=V(l.at,u)??a(`"${l.at}" is not a date and time such as 2026-10-11T14:30 or 2026-10-11T14:30+02:00.`),d<=i&&a(`${l.at} has already passed; it is now ${k(i)}.`)),d>i+o&&a(te(o,i)),{op:"created",id:e,due_at:d,prompt:t.prompt,reason:t.reason,time_zone:u,created_at:i,call_seq:r}}track(e){let t=e(),r=t.then(()=>{},()=>{});return this.inFlight.add(r),r.then(()=>this.inFlight.delete(r)),t}async catchUp(){try{await this.refreshed;let e=this.config.clock.now();await Promise.all(this.ctx.journal.summaries().filter(t=>{let r=O(this.ctx.journal,t.thread_id);return r!==null&&r<=e}).map(t=>this.check(t.thread_id)))}finally{this.armed=!0;for(let e of this.ctx.journal.summaries())this.arm(e.thread_id)}}arm(e){if(this.closed||this.retries.pending(e))return;let t=O(this.ctx.journal,e);t===null?this.queue.delete(e):this.queue.set(e,t,()=>{this.check(e)})}check(e){return this.track(()=>this.deliverDue(e))}async deliverDue(e){if(!this.closed)try{let t=this.ctx.threads.get(e);if(t===void 0)return;let r=this.config.clock.now(),s=await t.read(()=>[...this.ctx.journal.state(h,e).values()].filter(i=>w(i)==="pending"&&i.created.due_at<=r).map(i=>i.created.id)),o=(await Promise.allSettled(s.map(i=>this.fire(e,i)))).find(i=>i.status==="rejected");if(o!==void 0)throw o.reason;this.retries.succeeded(e),this.arm(e)}catch(t){if(this.ctx.reportError(t),this.closed)return;this.queue.delete(e),this.retries.failed(e,()=>{this.check(e)})}}async fire(e,t){if(this.closed)return;let r=this.thread(e),s=await r.read(()=>this.ctx.journal.state(h,e).get(t));if(s===void 0||w(s)!=="pending")return;let{created:u}=s,o=ee(t);await r.send({role:"user",content:we(u)},{id:o,source:$.record({schedule_id:t,due_at:u.due_at,summary:`${t} ${u.reason}`.slice(0,120)}),wake:{releases:!1,chain:"fresh"},target:"next-turn",rows:()=>{let i=this.ctx.journal.state(h,e).get(t);return i===void 0||w(i)!=="pending"?{refuse:{message:`Scheduled wake ${t} is no longer pending`}}:[g.entry({op:"fired",id:t,input_id:o},{threadId:e})]}})}async shutdown(){this.closed=!0,this.queue.dispose(),this.retries.dispose(),await Promise.allSettled([...this.inFlight])}};function we(n){return`<scheduled_wake ${[`schedule="${W(n.id)}"`,`due="${k(n.due_at)}"`,`time_zone="${W(n.time_zone)}"`,`reason="${W(n.reason)}"`].join(" ")}>
|
|
1
|
+
import{a as I}from"../../chunks/MNJ4SRF7.js";import{a as H,d as B}from"../../chunks/HK6ZSONF.js";import{a as Q}from"../../chunks/7VAPNE3L.js";import{b as z}from"../../chunks/EPYQ5M3B.js";import"../../chunks/5IRF47YB.js";import{b as y,c as G}from"../../chunks/JFX2MXBA.js";import{b as R,c as Z,d as L}from"../../chunks/S34ZA2R6.js";import{b as T}from"../../chunks/6FRNHMCV.js";import"../../chunks/CUK7CGC7.js";import{x as P}from"../../chunks/BPRCULZH.js";import"../../chunks/FJLU4UH3.js";import"../../chunks/DDANMY45.js";import"../../chunks/BBCW2FZK.js";import"../../chunks/MMNNZCBO.js";import"../../chunks/G44LG6VL.js";import"../../chunks/D4PQP5MA.js";import{Service as he}from"@leuduan/cordis";import{z as A}from"zod";import{z as c}from"zod";var x=c.number().int().nonnegative(),ae=c.discriminatedUnion("op",[c.strictObject({op:c.literal("created"),id:c.string().min(1),due_at:x,prompt:c.string().min(1),reason:c.string().min(1),time_zone:c.string().min(1),created_at:x,call_seq:c.number().int().positive().nullable()}),c.strictObject({op:c.literal("fired"),id:c.string().min(1),input_id:c.string().min(1)}),c.strictObject({op:c.literal("cancelled"),id:c.string().min(1),reason:c.string()})]),g=T({plugin:"lagent:schedules",kind:"schedule",version:1,schema:ae}),w=n=>n.cancelled?"cancelled":n.delivered===null?"pending":"fired",h=g.projection({key:"schedules",onFork:"reset",init:()=>new Map,apply(n,e){if(e.op==="created")return n.has(e.id)||n.set(e.id,{created:e,delivered:null,cancelled:!1}),n;let t=n.get(e.id);return t===void 0||n.set(e.id,e.op==="fired"?{...t,delivered:e.input_id}:{...t,cancelled:!0}),n}}),j=Q({key:"lagent:schedules",version:1,schema:c.strictObject({next_wake_at:x.nullable()}),applies:()=>!0,compute(n,e){let t=null;for(let r of n.state(h,e).values())w(r)==="pending"&&(t=t===null?r.created.due_at:Math.min(t,r.created.due_at));return{next_wake_at:t}}});function O(n,e){let t=j.read(n,e);return t.status==="available"?t.value.next_wake_at:null}var ce=c.strictObject({schedule_id:c.string().min(1),due_at:x,summary:c.string()}),$=T({plugin:"lagent:schedules",kind:"wake",version:1,schema:ce}),ue=n=>$.readSource(n);function J(n){try{return new Intl.DateTimeFormat("en-US",{timeZone:n}),!0}catch{return!1}}var Y=new Map;function le(n){let e=Y.get(n);return e===void 0&&(e=new Intl.DateTimeFormat("en-US",{timeZone:n,hourCycle:"h23",year:"numeric",month:"numeric",day:"numeric",hour:"numeric",minute:"numeric",second:"numeric"}),Y.set(n,e)),e}function M(n,e){let t={};for(let r of le(e).formatToParts(n))r.type!=="literal"&&(t[r.type]=Number(r.value));return{year:t.year,month:t.month,day:t.day,hour:t.hour,minute:t.minute,second:t.second}}function U(n,e){let t=M(n,e);return Date.UTC(t.year,t.month-1,t.day,t.hour,t.minute,t.second)-Math.floor(n/1e3)*1e3}var K=(n,e)=>n.year===e.year&&n.month===e.month&&n.day===e.day&&n.hour===e.hour&&n.minute===e.minute;function de(n,e){let t=Date.UTC(n.year,n.month-1,n.day,n.hour,n.minute),r=U(t-54e6,e),s=U(t+54e6,e),u=[...new Set([t-r,t-s])].sort((a,l)=>a-l);for(let a of u)if(K(M(a,e),n))return a;let o=Math.min(t-r,t-s),i=Math.max(t-r,t-s);for(;i-o>1e3;){let a=Math.floor((o+i)/2e3)*1e3;U(a,e)===s?i=a:o=a}return i}function V(n,e){let t=/^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2}))?(?:(Z)|([+-])(\d{2}):?(\d{2}))?$/.exec(n.trim());if(t===null)return null;let[r,s,u,o,i,a]=t.slice(1,7).map(_=>Number(_??0)),l=new Date(Date.UTC(r,s-1,u));if(l.getUTCFullYear()!==r||l.getUTCMonth()!==s-1||l.getUTCDate()!==u||o>23||i>59||a>59)return null;let[,,,,,,,d,p,S,ie]=t;if(d!==void 0||p!==void 0){let _=Number(S??0),q=Number(ie??0);if(_>14||q>59)return null;let oe=(p==="-"?-1:1)*(_*60+q)*6e4;return Date.UTC(r,s-1,u,o,i,a)-oe}let C={year:r,month:s,day:u,hour:o,minute:i},b=de(C,e);return K(M(b,e),C)?b+a*1e3:b}var X=A.number().int().positive(),me=A.strictObject({clock:H,timeZone:A.string().refine(J,"timeZone must be an IANA time zone"),maxDelayMs:X,maxPendingPerThread:X}),m=class extends Error{constructor(e){super(e),this.name="ScheduleRefusedError"}},D="Scheduled wakes are for conversations a person talks to; an agent working for another agent cannot schedule one.",fe="A scheduled wake can be created only in a turn a person started, not from a wake or a notice. Tell the person what to schedule, or ask them.",pe=n=>`sch-${n}`,ee=n=>`wake_${n}`,W=n=>n.replaceAll("&","&").replaceAll('"',""").replaceAll("<","<"),k=n=>new Date(n).toISOString().replace(".000Z","Z"),te=(n,e)=>`A scheduled wake may be due at most ${Math.round(n/6e4)} minutes from now, by ${k(e+n)}.`;function E(n,e,t){let{created:r}=e,s=e.delivered!==null&&!e.cancelled&&t.input(e.delivered).state==="queued";return{id:r.id,threadId:n,dueAt:r.due_at,timeZone:r.time_zone,prompt:r.prompt,reason:r.reason,status:w(e),unread:s,createdAt:r.created_at}}var v=class extends he{static inject=["threads","journal"];config;queue;inFlight=new Set;refreshed=Promise.resolve([]);armed=!1;retries;closed=!1;constructor(e,t){super(e,"schedules"),this.config=me.parse(t),this.queue=B(this.config.clock,r=>e.reportError(r)),this.retries=I(this.config.clock,r=>e.reportError(r)),e.effect(()=>e.journal.register(h)),e.effect(()=>e.journal.subscribe(r=>{this.armed&&g.read(r)!==void 0&&this.arm(r.thread_id)})),this.refreshed=j.register(e).refreshed,e.effect(()=>{let r=e.threads.registerOwner({owner:"lagent:schedules",repair:()=>this.catchUp()});return()=>r.dispose()}),R(e,()=>this.shutdown())}list({threadId:e}){let t=this.ctx.threads.view(e);return[...this.ctx.journal.state(h,e).values()].map(r=>E(e,r,t))}async create(e,t,r={}){let s=this.thread(e),u=r.callSeq===void 0?P("sch"):pe(r.callSeq),o=await s.read(l=>{let d=this.ctx.journal.state(h,e).get(u);if(d!==void 0)return E(e,d,l);if(s.origin!=null)throw new m(D);if(r.requirePersonTurn===!0&&!this.ctx.threads.chain(e).personTurn)throw new m(fe)});if(o!==void 0)return o;let i=this.createdRow(u,t,r.callSeq??null),{value:a}=await this.ctx.journal.transact(e,()=>{let l=this.ctx.journal.state(h,e);if(l.has(u))return{rows:[],value:null};let d=[...l.values()].filter(S=>w(S)==="pending").length;if(d>=this.config.maxPendingPerThread)return{rows:[],value:`This conversation already has ${d} scheduled wakes waiting, the most it may; cancel one with cancel_wake first.`};let p=this.config.clock.now();return i.due_at<=p?{rows:[],value:`${k(i.due_at)} has already passed; it is now ${k(p)}.`}:i.due_at>p+this.config.maxDelayMs?{rows:[],value:te(this.config.maxDelayMs,p)}:{rows:[g.entry({...i,created_at:p},{threadId:e})],value:null}});if(a!==null)throw new m(a);return s.read(l=>E(e,this.ctx.journal.state(h,e).get(u),l))}cancel(e,t,r="cancelled"){return this.track(async()=>{let s=this.thread(e);if(!await s.read(()=>this.ctx.journal.state(h,e).has(t)))throw new m(`No scheduled wake ${t} in this conversation.`);let o=await s.withdraw(ee(t),{rows:()=>this.ctx.journal.state(h,e).get(t).cancelled?[]:[g.entry({op:"cancelled",id:t,reason:r},{threadId:e})]});if(o.status==="refused")throw new Error(o.refusal.message);return{withdrawn:o.status==="withdrawn"}})}cancelRows(e,t="the conversation was stopped"){return[...this.ctx.journal.state(h,e).values()].filter(r=>w(r)==="pending").map(r=>g.entry({op:"cancelled",id:r.created.id,reason:t},{threadId:e}))}thread(e){let t=this.ctx.threads.get(e);if(t===void 0)throw new m(`No thread ${e}`);return t}createdRow(e,t,r){let{clock:s,timeZone:u,maxDelayMs:o}=this.config,i=s.now(),a=p=>{throw new m(p)};t.prompt.trim()===""&&a("A scheduled wake needs a prompt."),t.reason.trim()===""&&a("A scheduled wake needs a reason.");let{when:l}=t,d;return"in_seconds"in l?((!Number.isInteger(l.in_seconds)||l.in_seconds<1)&&a("in_seconds must be a whole number of at least 1."),d=i+l.in_seconds*1e3):(d=V(l.at,u)??a(`"${l.at}" is not a date and time such as 2026-10-11T14:30 or 2026-10-11T14:30+02:00.`),d<=i&&a(`${l.at} has already passed; it is now ${k(i)}.`)),d>i+o&&a(te(o,i)),{op:"created",id:e,due_at:d,prompt:t.prompt,reason:t.reason,time_zone:u,created_at:i,call_seq:r}}track(e){let t=e(),r=t.then(()=>{},()=>{});return this.inFlight.add(r),r.then(()=>this.inFlight.delete(r)),t}async catchUp(){try{await this.refreshed;let e=this.config.clock.now();await Promise.all(this.ctx.journal.summaries().filter(t=>{let r=O(this.ctx.journal,t.thread_id);return r!==null&&r<=e}).map(t=>this.check(t.thread_id)))}finally{this.armed=!0;for(let e of this.ctx.journal.summaries())this.arm(e.thread_id)}}arm(e){if(this.closed||this.retries.pending(e))return;let t=O(this.ctx.journal,e);t===null?this.queue.delete(e):this.queue.set(e,t,()=>{this.check(e)})}check(e){return this.track(()=>this.deliverDue(e))}async deliverDue(e){if(!this.closed)try{let t=this.ctx.threads.get(e);if(t===void 0)return;let r=this.config.clock.now(),s=await t.read(()=>[...this.ctx.journal.state(h,e).values()].filter(i=>w(i)==="pending"&&i.created.due_at<=r).map(i=>i.created.id)),o=(await Promise.allSettled(s.map(i=>this.fire(e,i)))).find(i=>i.status==="rejected");if(o!==void 0)throw o.reason;this.retries.succeeded(e),this.arm(e)}catch(t){if(this.ctx.reportError(t),this.closed)return;this.queue.delete(e),this.retries.failed(e,()=>{this.check(e)})}}async fire(e,t){if(this.closed)return;let r=this.thread(e),s=await r.read(()=>this.ctx.journal.state(h,e).get(t));if(s===void 0||w(s)!=="pending")return;let{created:u}=s,o=ee(t);await r.send({role:"user",content:we(u)},{id:o,source:$.record({schedule_id:t,due_at:u.due_at,summary:`${t} ${u.reason}`.slice(0,120)}),wake:{releases:!1,chain:"fresh"},target:"next-turn",rows:()=>{let i=this.ctx.journal.state(h,e).get(t);return i===void 0||w(i)!=="pending"?{refuse:{message:`Scheduled wake ${t} is no longer pending`}}:[g.entry({op:"fired",id:t,input_id:o},{threadId:e})]}})}async shutdown(){this.closed=!0,this.queue.dispose(),this.retries.dispose(),await Promise.allSettled([...this.inFlight])}};function we(n){return`<scheduled_wake ${[`schedule="${W(n.id)}"`,`due="${k(n.due_at)}"`,`time_zone="${W(n.time_zone)}"`,`reason="${W(n.reason)}"`].join(" ")}>
|
|
2
2
|
${n.prompt}
|
|
3
|
-
</scheduled_wake>`}import{z as f}from"zod";function N(n,e){let t=
|
|
3
|
+
</scheduled_wake>`}import{z as f}from"zod";function N(n,e){let t=Z(n.ctx,"schedules",e);if(n.ctx.threads.get(n.threadId)?.origin!=null)throw new m(D);return t}var ne=n=>`${n.id} ${n.unread?"delivered, not read yet":n.status} due ${k(n.dueAt)} (${n.timeZone}): ${n.reason}`,ge=f.strictObject({when:f.union([f.strictObject({in_seconds:f.number().int().min(1).describe("Seconds from now.")}),f.strictObject({at:f.string().describe("A date and time, such as 2026-10-11T14:30 (in the conversation\u2019s time zone) or 2026-10-11T14:30+02:00.")})]).describe("When: after in_seconds, or at a time."),prompt:f.string().min(1).describe("What you will be told when it fires: write it for yourself, then."),reason:f.string().min(1).describe("One line for the person, saying why.")}),F=y({name:"schedule_wake",description:"Schedule one wake of this conversation, after a delay or at a time. When it fires, you get its prompt as a new turn, even if nobody is here.",parameters:ge,onInterrupted:"rerun",executionMode:"exclusive",async execute(n,e){let t=await N(e,"schedule_wake").create(e.threadId,n,{callSeq:e.callSeq,requirePersonTurn:!0});return{output:`Scheduled ${ne(t)}`,metadata:{id:t.id,due_at:t.dueAt}}}}),re=y({name:"list_wakes",description:"List the scheduled wakes of this conversation still waiting to fire, and those that fired but whose message you have not read yet.",parameters:f.strictObject({}),onInterrupted:"rerun",executionMode:"parallel",execute:async(n,e)=>{let t=N(e,"list_wakes").list({threadId:e.threadId}),r=t.filter(s=>s.status==="pending"||s.unread);return{output:r.map(ne).join(`
|
|
4
4
|
`)||"No scheduled wakes waiting.",metadata:{pending:r.filter(s=>!s.unread).length,unread:r.filter(s=>s.unread).length,total:t.length}}}}),se=y({name:"cancel_wake",description:"Cancel a scheduled wake of this conversation, or its delivery still waiting to be read.",parameters:f.strictObject({id:f.string().min(1).describe("The id schedule_wake returned, such as sch-42.")}),onInterrupted:"rerun",executionMode:"exclusive",async execute({id:n},e){let{withdrawn:t}=await N(e,"cancel_wake").cancel(e.threadId,n,"cancel_wake");return{output:`Cancelled ${n}.${t?" Its waiting delivery was withdrawn.":""}`,metadata:{id:n,withdrawn:t}}}}),ke=["schedule_wake","list_wakes","cancel_wake"];var _e=["Use schedule_wake only when the person asks for something later, never to wait for what a notice will report, such as a sub-agent or a command started with notify.","A scheduled wake fires once, and can be created only in a turn the person started: never from a wake or a notice, so do not promise follow-ups you cannot schedule then.","When a wake fires you get its prompt as user input in a <scheduled_wake> frame naming its due time; compare it with the current date, since a wake waiting behind a stop is delivered late."].join(`
|
|
5
|
-
`),Ge={name:"lagent:schedules",inject:["tools","prompts","threads","journal"],async apply(n,e){
|
|
5
|
+
`),Ge={name:"lagent:schedules",inject:["tools","prompts","threads","journal"],async apply(n,e){L(n,"schedules"),await n.plugin(v,e);let t=[F,re,se];for(let r of t)G(n,r);z(n,{tag:"schedules",slot:"instruction",agents:"all",render:({tools:r})=>r.some(s=>s.tool===F)?_e:null})}};export{ke as SCHEDULE_TOOLS,m as ScheduleRefusedError,ue as readScheduledWake,Ge as schedules};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { PluginJournal } from '../../journal/index.js';
|
|
3
|
-
import type { ExtensionRecord } from '../../
|
|
3
|
+
import type { ExtensionRecord } from '../../records/journal.js';
|
|
4
4
|
import type { DeepReadonly } from '../../util/json.js';
|
|
5
5
|
/**
|
|
6
6
|
* What the journal keeps of a scheduled wake, on the thread it wakes, as `ext` rows of this plugin:
|
|
@@ -128,8 +128,8 @@ export declare const scheduleRow: {
|
|
|
128
128
|
op: "cancelled";
|
|
129
129
|
id: string;
|
|
130
130
|
reason: string;
|
|
131
|
-
}, options: import("../../journal/journal.js").AppendOptions): Promise<import("../../
|
|
132
|
-
read(event: import("../../
|
|
131
|
+
}, options: import("../../journal/journal.js").AppendOptions): Promise<import("../../records/journal.js").JournalEvent<"ext">>;
|
|
132
|
+
read(event: import("../../records/journal.js").JournalEvent): {
|
|
133
133
|
op: "created";
|
|
134
134
|
id: string;
|
|
135
135
|
due_at: number;
|
|
@@ -148,7 +148,7 @@ export declare const scheduleRow: {
|
|
|
148
148
|
reason: string;
|
|
149
149
|
} | undefined;
|
|
150
150
|
contribution(message: {
|
|
151
|
-
role?: import("../../
|
|
151
|
+
role?: import("../../records/journal.js").InputRole;
|
|
152
152
|
content: string | readonly import("../../llm/content.js").ContentPartInput[];
|
|
153
153
|
}, value: {
|
|
154
154
|
op: "created";
|
|
@@ -168,8 +168,8 @@ export declare const scheduleRow: {
|
|
|
168
168
|
id: string;
|
|
169
169
|
reason: string;
|
|
170
170
|
}, options?: {
|
|
171
|
-
sourceSeqs?: readonly import("../../
|
|
172
|
-
}): import("../../
|
|
171
|
+
sourceSeqs?: readonly import("../../records/journal.js").Seq[];
|
|
172
|
+
}): import("../../records/journal.js").StepContribution;
|
|
173
173
|
matchesSource(record: DeepReadonly<ExtensionRecord>): boolean;
|
|
174
174
|
rejection(reason: string, value: {
|
|
175
175
|
op: "created";
|
|
@@ -193,7 +193,7 @@ export declare const scheduleRow: {
|
|
|
193
193
|
reason: string;
|
|
194
194
|
source: ExtensionRecord;
|
|
195
195
|
};
|
|
196
|
-
readContribution: (event: import("../../
|
|
196
|
+
readContribution: (event: import("../../records/journal.js").JournalEvent) => {
|
|
197
197
|
op: "created";
|
|
198
198
|
id: string;
|
|
199
199
|
due_at: number;
|
|
@@ -229,7 +229,7 @@ export declare const scheduleRow: {
|
|
|
229
229
|
id: string;
|
|
230
230
|
reason: string;
|
|
231
231
|
} | undefined;
|
|
232
|
-
told: import("../../journal/projection.js").PluginProjection<import("../../
|
|
232
|
+
told: import("../../journal/projection.js").PluginProjection<import("../../services/extensionEvents.js").ToldNotices<{
|
|
233
233
|
op: "created";
|
|
234
234
|
id: string;
|
|
235
235
|
due_at: number;
|
|
@@ -267,7 +267,7 @@ export declare const scheduleRow: {
|
|
|
267
267
|
op: "cancelled";
|
|
268
268
|
id: string;
|
|
269
269
|
reason: string;
|
|
270
|
-
}, event: import("../../
|
|
270
|
+
}, event: import("../../records/journal.js").JournalEvent<"ext">): State;
|
|
271
271
|
onFork?: "inherit" | "reset";
|
|
272
272
|
}): import("../../journal/projection.js").PluginProjection<State>;
|
|
273
273
|
};
|
|
@@ -287,7 +287,7 @@ export declare const schedules: import("../../journal/projection.js").PluginProj
|
|
|
287
287
|
* What a thread contributes to its catalog entry: the earliest due instant among its pending wakes,
|
|
288
288
|
* `null` with none, so due work is found from the catalog without loading anything.
|
|
289
289
|
*/
|
|
290
|
-
export declare const nextWake: import("../../services/
|
|
290
|
+
export declare const nextWake: import("../../services/catalogContributions.js").ContributionDefinition<{
|
|
291
291
|
next_wake_at: number | null;
|
|
292
292
|
}>;
|
|
293
293
|
/**
|
|
@@ -339,22 +339,22 @@ export declare const scheduledWake: {
|
|
|
339
339
|
schedule_id: string;
|
|
340
340
|
due_at: number;
|
|
341
341
|
summary: string;
|
|
342
|
-
}, options: import("../../journal/journal.js").AppendOptions): Promise<import("../../
|
|
343
|
-
read(event: import("../../
|
|
342
|
+
}, options: import("../../journal/journal.js").AppendOptions): Promise<import("../../records/journal.js").JournalEvent<"ext">>;
|
|
343
|
+
read(event: import("../../records/journal.js").JournalEvent): {
|
|
344
344
|
schedule_id: string;
|
|
345
345
|
due_at: number;
|
|
346
346
|
summary: string;
|
|
347
347
|
} | undefined;
|
|
348
348
|
contribution(message: {
|
|
349
|
-
role?: import("../../
|
|
349
|
+
role?: import("../../records/journal.js").InputRole;
|
|
350
350
|
content: string | readonly import("../../llm/content.js").ContentPartInput[];
|
|
351
351
|
}, value: {
|
|
352
352
|
schedule_id: string;
|
|
353
353
|
due_at: number;
|
|
354
354
|
summary: string;
|
|
355
355
|
}, options?: {
|
|
356
|
-
sourceSeqs?: readonly import("../../
|
|
357
|
-
}): import("../../
|
|
356
|
+
sourceSeqs?: readonly import("../../records/journal.js").Seq[];
|
|
357
|
+
}): import("../../records/journal.js").StepContribution;
|
|
358
358
|
matchesSource(record: DeepReadonly<ExtensionRecord>): boolean;
|
|
359
359
|
rejection(reason: string, value: {
|
|
360
360
|
schedule_id: string;
|
|
@@ -365,7 +365,7 @@ export declare const scheduledWake: {
|
|
|
365
365
|
reason: string;
|
|
366
366
|
source: ExtensionRecord;
|
|
367
367
|
};
|
|
368
|
-
readContribution: (event: import("../../
|
|
368
|
+
readContribution: (event: import("../../records/journal.js").JournalEvent) => {
|
|
369
369
|
schedule_id: string;
|
|
370
370
|
due_at: number;
|
|
371
371
|
summary: string;
|
|
@@ -375,7 +375,7 @@ export declare const scheduledWake: {
|
|
|
375
375
|
due_at: number;
|
|
376
376
|
summary: string;
|
|
377
377
|
} | undefined;
|
|
378
|
-
told: import("../../journal/projection.js").PluginProjection<import("../../
|
|
378
|
+
told: import("../../journal/projection.js").PluginProjection<import("../../services/extensionEvents.js").ToldNotices<{
|
|
379
379
|
schedule_id: string;
|
|
380
380
|
due_at: number;
|
|
381
381
|
summary: string;
|
|
@@ -387,7 +387,7 @@ export declare const scheduledWake: {
|
|
|
387
387
|
schedule_id: string;
|
|
388
388
|
due_at: number;
|
|
389
389
|
summary: string;
|
|
390
|
-
}, event: import("../../
|
|
390
|
+
}, event: import("../../records/journal.js").JournalEvent<"ext">): State;
|
|
391
391
|
onFork?: "inherit" | "reset";
|
|
392
392
|
}): import("../../journal/projection.js").PluginProjection<State>;
|
|
393
393
|
};
|
|
@@ -9,7 +9,7 @@ declare module '@leuduan/cordis' {
|
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
export declare const SchedulesConfigSchema: z.ZodObject<{
|
|
12
|
-
clock: z.ZodCustom<import("../../
|
|
12
|
+
clock: z.ZodCustom<import("../../util/timers.js").Clock, import("../../util/timers.js").Clock>;
|
|
13
13
|
timeZone: z.ZodString;
|
|
14
14
|
maxDelayMs: z.ZodNumber;
|
|
15
15
|
maxPendingPerThread: z.ZodNumber;
|
|
@@ -57,7 +57,7 @@ export declare const iso: (instant: number) => string;
|
|
|
57
57
|
* a cancel each decide inside their own write, which the journal orders: a cancel that commits
|
|
58
58
|
* first stops the delivery, and one that commits after it withdraws the delivery, keyed by the
|
|
59
59
|
* wake, in the same write. Due work is found from the catalog (`next_wake_at`) and kept on one timer queue; at
|
|
60
|
-
* startup it fires through a repair-only constraint registration. Reasons in `docs/schedules.md`.
|
|
60
|
+
* startup it fires through a repair-only constraint registration. Reasons in `docs/records/schedules.md`.
|
|
61
61
|
*/
|
|
62
62
|
export declare class SchedulesService extends Service<SchedulesConfig> {
|
|
63
63
|
static inject: string[];
|