mixdog 1.2.6 → 1.3.1
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/LICENSES/simple-icons-CC0-1.0.txt +30 -0
- package/NOTICE.md +71 -9
- package/package.json +4 -1
- package/scripts/check-model-recommendations.mjs +89 -0
- package/scripts/gate-local.mjs +7 -0
- package/scripts/lib/isolated-test-env.mjs +16 -0
- package/src/agents/reviewer/AGENT.md +4 -2
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +10 -0
- package/src/defaults/skills/browser-use/SKILL.md +5 -0
- package/src/defaults/skills/memory-management/SKILL.md +4 -1
- package/src/defaults/skills/setup/references/actions.md +14 -4
- package/src/defaults/skills/setup-guide/SKILL.md +178 -0
- package/src/defaults/skills/setup-guide/model-recommendations.json +333 -0
- package/src/lib/rules-builder.cjs +55 -6
- package/src/output-styles/common.md +13 -8
- package/src/rules/agent/AGENT.md +4 -0
- package/src/rules/agent-mode/AGENT_MODE.md +24 -0
- package/src/rules/agent-mode/MIXY.md +28 -0
- package/src/rules/agent-mode/ROUTINE.md +6 -0
- package/src/rules/lead/LEAD.md +7 -3
- package/src/rules/shared/70-delivery.md +3 -0
- package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +86 -0
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +77 -14
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +241 -14
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +4 -2
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +216 -0
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -4
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +10 -1
- package/src/runtime/agent/orchestrator/mcp/client.mjs +115 -11
- package/src/runtime/agent/orchestrator/mcp/oauth-account.mjs +97 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +49 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +59 -9
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +32 -7
- package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +45 -6
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +195 -6
- package/src/runtime/agent/orchestrator/mcp/server-connectors.mjs +17 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +7 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +10 -3
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +7 -8
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/lib/tool-call-wire-ids.mjs +65 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +8 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +17 -12
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -0
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +14 -2
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +17 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +96 -1
- package/src/runtime/agent/orchestrator/runtime-core/context-status-shape.mjs +4 -0
- package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +6 -4
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +14 -0
- package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +12 -6
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +32 -5
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +21 -5
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +40 -7
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +52 -20
- package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/agent-home.d.mts +4 -0
- package/src/runtime/agent/orchestrator/session/agent-home.mjs +33 -0
- package/src/runtime/agent/orchestrator/session/agent-mode.mjs +62 -0
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +31 -0
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +241 -0
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +166 -0
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +650 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +192 -0
- package/src/runtime/agent/orchestrator/session/human-gate/held-message.mjs +95 -0
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +221 -0
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +195 -0
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +167 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +108 -6
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +74 -15
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +9 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +5 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +13 -3
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +49 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +18 -8
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-lifecycle-epoch.mjs +0 -8
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +44 -16
- package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +50 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +22 -5
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +39 -4
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +33 -24
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +58 -8
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/mixy-name.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +32 -5
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +86 -0
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +329 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/store-record-strict-scan.mjs +28 -9
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +16 -2
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -12
- package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +46 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +0 -4
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +58 -10
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +92 -15
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +12 -0
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +113 -5
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +4 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +25 -1
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +6 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +3 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +75 -57
- package/src/runtime/attachments/store.mjs +6 -0
- package/src/runtime/browser-bridge/client.mjs +24 -0
- package/src/runtime/channels/lib/scheduler.mjs +40 -12
- package/src/runtime/channels/lib/schedules-db.mjs +71 -12
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +36 -16
- package/src/runtime/channels/lib/webhook.mjs +0 -1
- package/src/runtime/channels/lib/webhooks-db.mjs +100 -10
- package/src/runtime/channels/lib/worker-main.mjs +17 -20
- package/src/runtime/computer-bridge/client.mjs +28 -1
- package/src/runtime/connectors/gmail-app-password.mjs +690 -0
- package/src/runtime/connectors/google-mcp.mjs +446 -0
- package/src/runtime/effort-judge/judge-client.mjs +1 -1
- package/src/runtime/effort-judge/model-manifest.json +7 -7
- package/src/runtime/memory/lib/core-memory-access.mjs +69 -0
- package/src/runtime/memory/lib/core-memory-file.mjs +16 -8
- package/src/runtime/memory/lib/core-memory-index.mjs +15 -9
- package/src/runtime/memory/lib/core-memory-management.mjs +10 -4
- package/src/runtime/memory/lib/core-memory-store.mjs +53 -30
- package/src/runtime/memory/lib/core-memory-uniqueness.mjs +10 -6
- package/src/runtime/memory/lib/http-router/session-routes.mjs +3 -2
- package/src/runtime/memory/lib/memory-action-handlers/core-actions.mjs +44 -14
- package/src/runtime/memory/lib/memory-schema/core-and-meta.mjs +11 -0
- package/src/runtime/memory/lib/memory.mjs +7 -1
- package/src/runtime/memory/lib/query-core-recall.mjs +9 -1
- package/src/runtime/memory/lib/query-hybrid-search.mjs +2 -0
- package/src/runtime/memory/lib/tool-call-handler.mjs +2 -0
- package/src/runtime/memory/tool-defs.mjs +5 -0
- package/src/runtime/shared/agent-avatar-sprites.mjs +445 -0
- package/src/runtime/shared/agent-avatar.d.mts +64 -0
- package/src/runtime/shared/agent-avatar.mjs +320 -0
- package/src/runtime/shared/agent-kind.mjs +152 -0
- package/src/runtime/shared/agent-route-config.d.mts +4 -0
- package/src/runtime/shared/agent-route-config.mjs +30 -0
- package/src/runtime/shared/agent-store.mjs +132 -0
- package/src/runtime/shared/atomic-file.mjs +14 -1
- package/src/runtime/shared/bot-split-migration.mjs +457 -0
- package/src/runtime/shared/config-patch.mjs +20 -1
- package/src/runtime/shared/config.mjs +8 -1
- package/src/runtime/shared/connector-accounts.mjs +15 -0
- package/src/runtime/shared/connector-catalog.d.mts +51 -0
- package/src/runtime/shared/connector-catalog.mjs +228 -0
- package/src/runtime/shared/connector-triggers.d.mts +3 -0
- package/src/runtime/shared/connector-triggers.mjs +45 -0
- package/src/runtime/shared/err-text.mjs +3 -1
- package/src/runtime/shared/error-presentation.mjs +91 -6
- package/src/runtime/shared/google-oauth-client.mjs +24 -0
- package/src/runtime/shared/handoff-artifacts.d.mts +20 -0
- package/src/runtime/shared/handoff-artifacts.mjs +61 -0
- package/src/runtime/shared/held-request-host.d.mts +12 -0
- package/src/runtime/shared/held-request-host.mjs +71 -0
- package/src/runtime/shared/llm/provider-model-identities.mjs +6 -1
- package/src/runtime/shared/llm/usage-accounting.mjs +7 -1
- package/src/runtime/shared/model-route-fallback.mjs +79 -7
- package/src/runtime/shared/plain-preview.d.mts +1 -0
- package/src/runtime/shared/plain-preview.mjs +33 -0
- package/src/runtime/shared/routine-limits.d.mts +11 -0
- package/src/runtime/shared/routine-limits.mjs +21 -0
- package/src/runtime/shared/routine-trigger.d.mts +10 -0
- package/src/runtime/shared/routine-trigger.mjs +44 -0
- package/src/runtime/shared/schedule-model-ref.mjs +9 -10
- package/src/runtime/shared/sensitive-path-rules.d.mts +15 -0
- package/src/runtime/shared/sensitive-path-rules.mjs +256 -0
- package/src/runtime/shared/slow-span.mjs +4 -12
- package/src/runtime/shared/statusline/statusline-segments.mjs +13 -1
- package/src/runtime/shared/transcript-metadata.mjs +10 -0
- package/src/runtime/shared/turn-changes.mjs +0 -8
- package/src/runtime/shared/usage-readout-totals.mjs +58 -0
- package/src/runtime/shared/user-data-guard.mjs +119 -38
- package/src/session-runtime/agent-home-delivery.mjs +826 -0
- package/src/session-runtime/agent-profile-changes.mjs +21 -0
- package/src/session-runtime/agent-room-delivery.mjs +133 -0
- package/src/session-runtime/agent-task-sessions.mjs +460 -0
- package/src/session-runtime/boot/apis.mjs +63 -18
- package/src/session-runtime/boot/facade.mjs +5 -8
- package/src/session-runtime/boot/infrastructure.mjs +28 -17
- package/src/session-runtime/boot/providers.mjs +14 -4
- package/src/session-runtime/boot/tools.mjs +3 -0
- package/src/session-runtime/channel-config-api.mjs +53 -5
- package/src/session-runtime/config-lifecycle.mjs +43 -2
- package/src/session-runtime/cwd-plugins/core-memory-context.mjs +36 -20
- package/src/session-runtime/cwd-plugins/plugin-status.mjs +6 -1
- package/src/session-runtime/handoff-attachments.mjs +593 -0
- package/src/session-runtime/held-requests.mjs +228 -0
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +121 -0
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +5 -1
- package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +31 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +56 -3
- package/src/session-runtime/internal-tool-executor.mjs +2 -0
- package/src/session-runtime/lifecycle/session-catalog.mjs +9 -0
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +4 -0
- package/src/session-runtime/lifecycle/shared.mjs +6 -0
- package/src/session-runtime/mcp-glue/server-catalog.mjs +8 -0
- package/src/session-runtime/mcp-glue/server-input.mjs +3 -0
- package/src/session-runtime/mcp-oauth-login.mjs +112 -15
- package/src/session-runtime/model-route/route-persist.mjs +2 -2
- package/src/session-runtime/model-route/route-tuning.mjs +7 -1
- package/src/session-runtime/model-route/set-route.mjs +35 -4
- package/src/session-runtime/native-web-search.mjs +2 -2
- package/src/session-runtime/new-session-config.mjs +6 -1
- package/src/session-runtime/owned-automation-run.mjs +229 -0
- package/src/session-runtime/resource-api.mjs +5 -0
- package/src/session-runtime/resource-mcp-api.mjs +107 -13
- package/src/session-runtime/resource-skills-api.mjs +12 -0
- package/src/session-runtime/room-member-gate.mjs +48 -0
- package/src/session-runtime/route-resolution.mjs +5 -11
- package/src/session-runtime/routine-home-turn.mjs +156 -0
- package/src/session-runtime/routine-owner.mjs +48 -0
- package/src/session-runtime/schedule-session-run.mjs +30 -64
- package/src/session-runtime/services/agent-tool/helpers.mjs +30 -1
- package/src/session-runtime/services/agent-tool/send-flow.mjs +11 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +19 -4
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +2 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +5 -8
- package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
- package/src/session-runtime/services/channel-admin.mjs +93 -24
- package/src/session-runtime/services/memory-access.mjs +37 -0
- package/src/session-runtime/services/provider-admin.mjs +74 -39
- package/src/session-runtime/services/schedule-access.mjs +91 -0
- package/src/session-runtime/session-create/session-options.mjs +34 -7
- package/src/session-runtime/session-hooks.mjs +16 -8
- package/src/session-runtime/setup-tool/executor.mjs +79 -6
- package/src/session-runtime/setup-tool/extended-actions.mjs +90 -20
- package/src/session-runtime/setup-tool/settings-contract.mjs +14 -6
- package/src/session-runtime/setup-tool/tool-defs.mjs +8 -0
- package/src/session-runtime/skills-api.mjs +16 -1
- package/src/session-runtime/tool-policy-refresh.mjs +249 -29
- package/src/session-runtime/turn/session-ops.mjs +8 -1
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +10 -2
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +14 -0
- package/src/session-runtime/webhook-session-run.mjs +49 -67
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +239 -0
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +650 -25
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +67 -7
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +11 -4
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +11 -1
- package/src/session-runtime/workflow-agents-api.mjs +4 -1
- package/src/standalone/daemon-agent-control.mjs +1 -8
- package/src/standalone/daemon-session-control.mjs +21 -4
- package/src/standalone/daemon.mjs +31 -5
- package/src/standalone/session-protocol.mjs +27 -0
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +46 -3
- package/src/standalone/session-service/agent-tree.mjs +4 -3
- package/src/standalone/session-service/session-calls/session-turns.mjs +28 -0
- package/src/standalone/session-service.mjs +19 -0
- package/src/tui/app/slash-commands.mjs +20 -0
- package/src/tui/dist/index.mjs +234 -95
- package/src/tui/session/context-state.mjs +6 -1
- package/src/tui/session/notice-surface.mjs +34 -4
- package/src/tui/session/queue-helpers.mjs +2 -1
- package/src/tui/session/session-api/extensions.mjs +4 -0
- package/src/tui/session/session-api/intake/submission.mjs +5 -0
- package/src/tui/session/session-api/integrations.mjs +8 -0
- package/src/tui/session/session-api/lifecycle/session-reset.mjs +13 -3
- package/src/tui/session/session-api/lifecycle.mjs +17 -1
- package/src/tui/session/session-api/routes.mjs +61 -5
- package/src/tui/session/session-flow/drain.mjs +12 -3
- package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -1
- package/src/tui/session/session-flow/queue/take-entries.mjs +3 -2
- package/src/tui/session/session-flow/steering.mjs +58 -24
- package/src/tui/session/tool-approval.mjs +26 -5
- package/src/tui/session/transcript-intake.mjs +8 -2
- package/src/tui/session/transcript-spill.mjs +5 -1
- package/src/tui/session/tui-steering-persist.mjs +65 -42
- package/src/tui/session/turn-live-call.mjs +64 -0
- package/src/tui/session/turn.mjs +34 -8
- package/src/session-runtime/automation-agents.mjs +0 -94
- package/src/session-runtime/automation-workflow.mjs +0 -59
|
@@ -104,6 +104,9 @@ export function createShellRun({ abortSignal, resolve, jobId = null }) {
|
|
|
104
104
|
// `settled`: whichever transition wins first wins for good.
|
|
105
105
|
autoBackgrounded: false,
|
|
106
106
|
autoBackgroundJobId: null,
|
|
107
|
+
// [exitCode, signal] of a 'close' that arrived while promotion was in
|
|
108
|
+
// flight; a promotion that then fails settles the run with it.
|
|
109
|
+
promotionClose: null,
|
|
107
110
|
autoBgTimer: null,
|
|
108
111
|
killUnconfirmed: false,
|
|
109
112
|
child: null,
|
|
@@ -288,6 +288,7 @@ async function settleQueuedPromotion(jobId) {
|
|
|
288
288
|
function queueSaturatedAsTask(run, saturation, spec) {
|
|
289
289
|
const queuedAbort = new AbortController();
|
|
290
290
|
const taskId = run.foregroundRecordId;
|
|
291
|
+
let promotedJobId = null;
|
|
291
292
|
let task;
|
|
292
293
|
try {
|
|
293
294
|
task = startBackgroundTask({
|
|
@@ -301,10 +302,15 @@ function queueSaturatedAsTask(run, saturation, spec) {
|
|
|
301
302
|
input: { command: spec.command, cwd: spec.cwd },
|
|
302
303
|
meta: { task_id: taskId, stdout: null, stderr: null, cwd: spec.cwd, timeoutMs: spec.backgroundDeadlineMs || 0 },
|
|
303
304
|
resultType: 'shell_task_result',
|
|
305
|
+
// While the command runs in the foreground, its abort handler snapshots
|
|
306
|
+
// the tree before it kills. A native kill sent here as well would end
|
|
307
|
+
// the root first: the snapshot then misses the tree and the workspace
|
|
308
|
+
// stays held as unconfirmed forever. Only a promoted job is killed here.
|
|
304
309
|
cancel: () => {
|
|
305
310
|
queuedAbort.abort();
|
|
311
|
+
if (!promotedJobId) return;
|
|
306
312
|
try {
|
|
307
|
-
killShellJob(
|
|
313
|
+
killShellJob(promotedJobId);
|
|
308
314
|
} catch {}
|
|
309
315
|
},
|
|
310
316
|
run: async () => {
|
|
@@ -319,7 +325,17 @@ function queueSaturatedAsTask(run, saturation, spec) {
|
|
|
319
325
|
onProgress: null,
|
|
320
326
|
onOutputTail: null,
|
|
321
327
|
});
|
|
322
|
-
if (result.backgrounded && result.jobId)
|
|
328
|
+
if (result.backgrounded && result.jobId) {
|
|
329
|
+
promotedJobId = result.jobId;
|
|
330
|
+
// Promotion detached the abort handler: a cancel that landed during
|
|
331
|
+
// the handoff reaches the promoted job here.
|
|
332
|
+
if (queuedAbort.signal.aborted) {
|
|
333
|
+
try {
|
|
334
|
+
killShellJob(promotedJobId);
|
|
335
|
+
} catch {}
|
|
336
|
+
}
|
|
337
|
+
return settleQueuedPromotion(promotedJobId);
|
|
338
|
+
}
|
|
323
339
|
const output = [result.stdout, result.stderr].filter(Boolean).join('\n');
|
|
324
340
|
if (result.exitCode !== 0) {
|
|
325
341
|
throw new Error(`exit code ${result.exitCode ?? result.signal ?? 'unknown'}${output ? `\n${output}` : ''}`);
|
|
@@ -573,65 +589,67 @@ export function execShellCommand(options) {
|
|
|
573
589
|
// The user approved this call's elevation 'ask' decision (runtime-set).
|
|
574
590
|
elevationApproved = false,
|
|
575
591
|
} = options;
|
|
576
|
-
return new Promise(
|
|
592
|
+
return new Promise((resolve) => {
|
|
577
593
|
const run = createShellRun({ abortSignal, resolve, jobId });
|
|
578
|
-
//
|
|
579
|
-
// unhandled rejection
|
|
580
|
-
//
|
|
581
|
-
//
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
594
|
+
// A throw inside this async body is NOT routed to the Promise — it becomes
|
|
595
|
+
// an unhandled rejection while the awaiting caller hangs forever. Every
|
|
596
|
+
// path below therefore has to settle the run itself, including the
|
|
597
|
+
// unexpected one.
|
|
598
|
+
void (async () => {
|
|
599
|
+
try {
|
|
600
|
+
const spawned = await admitAndSpawn(
|
|
601
|
+
run,
|
|
602
|
+
{
|
|
603
|
+
admission,
|
|
604
|
+
shell,
|
|
605
|
+
shellArg,
|
|
606
|
+
shellArgs,
|
|
607
|
+
command,
|
|
608
|
+
env,
|
|
609
|
+
cwd,
|
|
610
|
+
abortSignal,
|
|
611
|
+
directArgv,
|
|
612
|
+
execScript,
|
|
613
|
+
elevationApproved,
|
|
614
|
+
ownerSessionId,
|
|
615
|
+
clientHostPid,
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
admissionWaitMs,
|
|
619
|
+
onSaturated: (err) => backgroundOnTimeout && queueSaturatedAsTask(run, err, options),
|
|
620
|
+
}
|
|
621
|
+
);
|
|
622
|
+
if (!spawned) return;
|
|
623
|
+
|
|
624
|
+
// Pre-aborted signal: kill immediately if the abort already fired
|
|
625
|
+
// before spawn returned (synchronous reentry from a parent abort), so
|
|
626
|
+
// the child doesn't run for the full timeoutMs window.
|
|
627
|
+
if (abortSignal?.aborted) {
|
|
628
|
+
treeKillForceSettle(run, 'cancellation');
|
|
629
|
+
}
|
|
630
|
+
attachOutputCapture(run);
|
|
631
|
+
const releasePromotedCapture = createPromotedCaptureRelease(run);
|
|
632
|
+
run.settle = createSettle(run, releasePromotedCapture);
|
|
633
|
+
attachChildExit(run, releasePromotedCapture);
|
|
634
|
+
const fireAutoBackground = createAutoBackground({
|
|
635
|
+
run,
|
|
590
636
|
command,
|
|
591
|
-
env,
|
|
592
637
|
cwd,
|
|
593
|
-
abortSignal,
|
|
594
|
-
directArgv,
|
|
595
|
-
execScript,
|
|
596
|
-
elevationApproved,
|
|
597
|
-
ownerSessionId,
|
|
598
638
|
clientHostPid,
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
639
|
+
ownerSessionId,
|
|
640
|
+
promotedTimeoutMs,
|
|
641
|
+
backgroundDeadlineMs,
|
|
642
|
+
});
|
|
643
|
+
armDeadlines(
|
|
644
|
+
run,
|
|
645
|
+
{ timeoutMs, autoBackgroundMs, autoBackgroundReason, backgroundOnTimeout, abortSignal },
|
|
646
|
+
fireAutoBackground
|
|
647
|
+
);
|
|
648
|
+
armInputWake(run, wakeSignal, fireAutoBackground);
|
|
649
|
+
armProgressTimers(run, { onProgress, onOutputTail });
|
|
650
|
+
} catch (err) {
|
|
651
|
+
settleSetupFailure(run, err);
|
|
612
652
|
}
|
|
613
|
-
|
|
614
|
-
const releasePromotedCapture = createPromotedCaptureRelease(run);
|
|
615
|
-
run.settle = createSettle(run, releasePromotedCapture);
|
|
616
|
-
attachChildExit(run, releasePromotedCapture);
|
|
617
|
-
const fireAutoBackground = createAutoBackground({
|
|
618
|
-
run,
|
|
619
|
-
command,
|
|
620
|
-
cwd,
|
|
621
|
-
clientHostPid,
|
|
622
|
-
ownerSessionId,
|
|
623
|
-
promotedTimeoutMs,
|
|
624
|
-
backgroundDeadlineMs,
|
|
625
|
-
});
|
|
626
|
-
armDeadlines(
|
|
627
|
-
run,
|
|
628
|
-
{ timeoutMs, autoBackgroundMs, autoBackgroundReason, backgroundOnTimeout, abortSignal },
|
|
629
|
-
fireAutoBackground
|
|
630
|
-
);
|
|
631
|
-
armInputWake(run, wakeSignal, fireAutoBackground);
|
|
632
|
-
armProgressTimers(run, { onProgress, onOutputTail });
|
|
633
|
-
} catch (err) {
|
|
634
|
-
settleSetupFailure(run, err);
|
|
635
|
-
}
|
|
653
|
+
})();
|
|
636
654
|
});
|
|
637
655
|
}
|
|
@@ -166,6 +166,12 @@ function saveBuffer(buffer) {
|
|
|
166
166
|
return { attachmentRef, sizeBytes: buffer.length };
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
/** Store `buffer` (a file a bot or agent hands on, session-runtime/
|
|
170
|
+
* handoff-attachments.mjs) under its content address: `{ attachmentRef, sizeBytes }`. */
|
|
171
|
+
export function storeAttachmentBuffer(buffer) {
|
|
172
|
+
return saveBuffer(buffer);
|
|
173
|
+
}
|
|
174
|
+
|
|
169
175
|
export function isAttachmentReference(value) {
|
|
170
176
|
return Boolean(value && typeof value === 'object' && ATTACHMENT_REF_RE.test(String(value.attachmentRef || '')));
|
|
171
177
|
}
|
|
@@ -53,6 +53,30 @@ export async function finishBrowserTurn(sessionId, turnId, { aborted = false } =
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/** Runtime-only: fill the secret the user handed the desktop for `requestId`
|
|
57
|
+
* into the page's focused password field through the host's isolated-world
|
|
58
|
+
* fill. The value never passes through here; the host answers only
|
|
59
|
+
* 'provided' or 'skipped'. */
|
|
60
|
+
export async function browserSecretInput(sessionId, requestId, { signal } = {}) {
|
|
61
|
+
const discovery = readBridgeDiscovery(DISCOVERY_FILE);
|
|
62
|
+
if (!discovery || !sessionId || !requestId) return 'skipped';
|
|
63
|
+
try {
|
|
64
|
+
const result = await requestBridge(
|
|
65
|
+
discovery,
|
|
66
|
+
JSON.stringify({ action: 'secret_input', session_id: String(sessionId), request_id: String(requestId) }),
|
|
67
|
+
signal
|
|
68
|
+
? AbortSignal.any([AbortSignal.timeout(REQUEST_TIMEOUT_MS), signal])
|
|
69
|
+
: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
70
|
+
{ sessionId, action: 'secret_input' }
|
|
71
|
+
);
|
|
72
|
+
return result.status === 200 && result.body?.ok !== false && result.body?.value?.text === 'provided'
|
|
73
|
+
? 'provided'
|
|
74
|
+
: 'skipped';
|
|
75
|
+
} catch {
|
|
76
|
+
return 'skipped';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
56
80
|
const BRIDGE_UNAVAILABLE_MESSAGE = 'browser use is unavailable; open the Mixdog desktop app and enable Browser Use';
|
|
57
81
|
|
|
58
82
|
/** Sync gate for the session tool surface (featureDisallowedTools). */
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
markFired,
|
|
13
13
|
markDone,
|
|
14
14
|
markScheduleFailure,
|
|
15
|
+
markScheduleSkipped,
|
|
15
16
|
markScheduleSuccess,
|
|
16
17
|
setDeferred,
|
|
17
18
|
setNextFire,
|
|
@@ -61,6 +62,11 @@ function isCronExpression(time) {
|
|
|
61
62
|
return false;
|
|
62
63
|
}
|
|
63
64
|
}
|
|
65
|
+
// True when this fire (started at `since`) ended in an overlap skip.
|
|
66
|
+
function skippedSince(schedule, since) {
|
|
67
|
+
const at = schedule.lastSkippedAt ? new Date(schedule.lastSkippedAt).getTime() : 0;
|
|
68
|
+
return at >= since.getTime();
|
|
69
|
+
}
|
|
64
70
|
// Records a one-shot fire that did not complete; the entry stays pending.
|
|
65
71
|
async function markOneShotFailed(schedule) {
|
|
66
72
|
schedule.lastFailedAt = new Date().toISOString();
|
|
@@ -407,6 +413,8 @@ ${prompt}`;
|
|
|
407
413
|
schedule.lastFiredAt = startedAt.toISOString();
|
|
408
414
|
this.lastFired.set(schedule.name, startedAt.toISOString());
|
|
409
415
|
const fired = await this.fireTimed(schedule, type, { awaitDispatch: true });
|
|
416
|
+
// An overlap skip is recorded as a skip, never as a failed run.
|
|
417
|
+
if (!fired && skippedSince(schedule, startedAt)) return;
|
|
410
418
|
if (!fired) {
|
|
411
419
|
schedule.lastFailedAt = new Date().toISOString();
|
|
412
420
|
await markScheduleFailure(schedule.name, new Date());
|
|
@@ -498,7 +506,7 @@ ${prompt}`;
|
|
|
498
506
|
return;
|
|
499
507
|
}
|
|
500
508
|
// Mark done ONLY after a successful fire. A false/throwing fireTimed
|
|
501
|
-
// (missing prompt
|
|
509
|
+
// (missing prompt, running guard, paused owner, dispatch error) must leave the
|
|
502
510
|
// one-shot pending so a reload/restart can retry it — never retire it.
|
|
503
511
|
try {
|
|
504
512
|
// awaitDispatch: for a one-shot we must wait for the actual LLM/relay
|
|
@@ -506,6 +514,10 @@ ${prompt}`;
|
|
|
506
514
|
// contract) would retire the entry even when schedulerLlm() later
|
|
507
515
|
// rejects. Only a resolved dispatch counts as a real fire here.
|
|
508
516
|
const fired = await this.fireTimed(schedule, type, { awaitDispatch: true });
|
|
517
|
+
if (!fired && skippedSince(schedule, now)) {
|
|
518
|
+
logSchedule(`one-shot "${schedule.name}" skipped (previous run in progress) — leaving pending for retry\n`);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
509
521
|
if (!fired) {
|
|
510
522
|
await markOneShotFailed(schedule);
|
|
511
523
|
logSchedule(`one-shot "${schedule.name}" did not fire (skipped/guarded) — leaving pending for retry\n`);
|
|
@@ -625,20 +637,11 @@ ${prompt}`;
|
|
|
625
637
|
async fireTimedPrompt(schedule, type, prompt, { awaitDispatch = false } = {}) {
|
|
626
638
|
logSchedule(`firing ${schedule.name} (${type})\n`);
|
|
627
639
|
if (this.running.has(schedule.name)) {
|
|
628
|
-
|
|
629
|
-
// scheduler "ignoring" a fire.
|
|
630
|
-
logSchedule(`${schedule.name}: skipped — previous run still in progress\n`);
|
|
640
|
+
this.recordSkip(schedule);
|
|
631
641
|
return false;
|
|
632
642
|
}
|
|
633
643
|
if (type === 'interactive' && this.injectIntoLead(schedule, type, prompt)) return true;
|
|
634
644
|
this.running.add(schedule.name);
|
|
635
|
-
const presetId = schedule.model;
|
|
636
|
-
if (!presetId) {
|
|
637
|
-
this.running.delete(schedule.name);
|
|
638
|
-
logSchedule(`${schedule.name}: missing required "model" in schedule config — dispatch rejected\n`);
|
|
639
|
-
this.notifyFailure(schedule, 'missing "model" in schedule config — dispatch rejected');
|
|
640
|
-
return false;
|
|
641
|
-
}
|
|
642
645
|
// Cron (recurring) keeps the fire-and-forget contract: return truthy now
|
|
643
646
|
// and swallow async failures. A one-shot (awaitDispatch) instead awaits
|
|
644
647
|
// the dispatch so a rejected run propagates and the caller leaves the
|
|
@@ -676,8 +679,22 @@ ${prompt}`;
|
|
|
676
679
|
? this.scheduleRunner(schedule, { prompt })
|
|
677
680
|
: Promise.reject(new Error('schedule runner not configured'));
|
|
678
681
|
return run
|
|
679
|
-
.then((
|
|
682
|
+
.then((outcome) => {
|
|
680
683
|
this.running.delete(schedule.name);
|
|
684
|
+
// The run path found this schedule's previous run still going (a
|
|
685
|
+
// "run now" from the app) or backing off after failures in a row; it
|
|
686
|
+
// already persisted the skip.
|
|
687
|
+
if (outcome?.skipped) {
|
|
688
|
+
schedule.lastSkippedAt = new Date().toISOString();
|
|
689
|
+
const why =
|
|
690
|
+
outcome.reason === 'backoff'
|
|
691
|
+
? 'backing off after failures'
|
|
692
|
+
: outcome.reason === 'owner'
|
|
693
|
+
? `paused: ${outcome.message}`
|
|
694
|
+
: 'previous run still in progress';
|
|
695
|
+
logSchedule(`${schedule.name}: skipped — ${why}\n`);
|
|
696
|
+
return false;
|
|
697
|
+
}
|
|
681
698
|
logSchedule(`${schedule.name} done\n`);
|
|
682
699
|
return true;
|
|
683
700
|
})
|
|
@@ -693,6 +710,17 @@ ${prompt}`;
|
|
|
693
710
|
});
|
|
694
711
|
}
|
|
695
712
|
// ── Helpers ─────────────────────────────────────────────────────────
|
|
713
|
+
/** Overlap skip: the previous run of this schedule is still in progress.
|
|
714
|
+
* Logged and persisted (last_skipped_at) so a skipped fire is never
|
|
715
|
+
* invisible; `persistSkip` is injectable for tests. */
|
|
716
|
+
recordSkip(schedule) {
|
|
717
|
+
const at = new Date();
|
|
718
|
+
schedule.lastSkippedAt = at.toISOString();
|
|
719
|
+
logSchedule(`${schedule.name}: skipped — previous run still in progress\n`);
|
|
720
|
+
void Promise.resolve()
|
|
721
|
+
.then(() => (this.persistSkip ?? markScheduleSkipped)(schedule.name, at))
|
|
722
|
+
.catch((err) => process.stderr.write(`mixdog scheduler: ${schedule.name} skip record failed: ${err}\n`));
|
|
723
|
+
}
|
|
696
724
|
/** Failure notice: logged so scheduled fires never fail silently. */
|
|
697
725
|
notifyFailure(schedule, reason) {
|
|
698
726
|
logSchedule(`schedule "${schedule?.name}": ${reason}\n`);
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { ensurePgInstance, withSchemaBootstrapLock } from '../../memory/lib/pg/adapter.mjs';
|
|
15
15
|
import { createPgSchemaDb } from '../../shared/pg-schema-db.mjs';
|
|
16
16
|
import { resolvePluginData } from '../../shared/plugin-paths.mjs';
|
|
17
|
+
import { ROUTINE_LIMITS } from '../../shared/routine-limits.mjs';
|
|
17
18
|
|
|
18
19
|
const SCHEMA = 'scheduler';
|
|
19
20
|
|
|
@@ -54,6 +55,18 @@ ALTER TABLE scheduler.schedules ADD COLUMN IF NOT EXISTS last_scheduled_at times
|
|
|
54
55
|
ALTER TABLE scheduler.schedules ADD COLUMN IF NOT EXISTS last_started_at timestamptz;
|
|
55
56
|
ALTER TABLE scheduler.schedules ADD COLUMN IF NOT EXISTS last_success_at timestamptz;
|
|
56
57
|
ALTER TABLE scheduler.schedules ADD COLUMN IF NOT EXISTS last_failed_at timestamptz;
|
|
58
|
+
ALTER TABLE scheduler.schedules ADD COLUMN IF NOT EXISTS last_skipped_at timestamptz;
|
|
59
|
+
-- The agent a schedule belongs to (its results land in that agent's home
|
|
60
|
+
-- thread). Rows from before ownership existed fill with Mixy ('lead').
|
|
61
|
+
ALTER TABLE scheduler.schedules ADD COLUMN IF NOT EXISTS owner_agent_id text NOT NULL DEFAULT 'lead';
|
|
62
|
+
-- Legacy columns model, workflow and run_as_agent are no longer read or written (kept, not dropped, to avoid a risky migration).
|
|
63
|
+
ALTER TABLE scheduler.schedules ADD COLUMN IF NOT EXISTS run_as_agent boolean NOT NULL DEFAULT false;
|
|
64
|
+
-- Failed runs in a row: a success or switching it back on resets it; at
|
|
65
|
+
-- ROUTINE_LIMITS.pauseAfterFailures the schedule switches itself off.
|
|
66
|
+
ALTER TABLE scheduler.schedules ADD COLUMN IF NOT EXISTS consecutive_failures integer NOT NULL DEFAULT 0;
|
|
67
|
+
-- Results of finished runs not yet queued for the owner's home thread, by run
|
|
68
|
+
-- id ({ state: 'pending' | 'failed', error?, at, result }).
|
|
69
|
+
ALTER TABLE scheduler.schedules ADD COLUMN IF NOT EXISTS result_delivery jsonb;
|
|
57
70
|
`;
|
|
58
71
|
|
|
59
72
|
const getDb = createPgSchemaDb({
|
|
@@ -78,9 +91,7 @@ function rowToDef(row) {
|
|
|
78
91
|
timezone: row.timezone,
|
|
79
92
|
target: row.target,
|
|
80
93
|
channelId: row.channel_id,
|
|
81
|
-
model: row.model,
|
|
82
94
|
cwd: row.cwd,
|
|
83
|
-
workflow: row.workflow,
|
|
84
95
|
attachments: row.attachments || null,
|
|
85
96
|
delivery: row.delivery || null,
|
|
86
97
|
prompt: row.prompt,
|
|
@@ -91,6 +102,10 @@ function rowToDef(row) {
|
|
|
91
102
|
lastStartedAt: row.last_started_at,
|
|
92
103
|
lastSuccessAt: row.last_success_at,
|
|
93
104
|
lastFailedAt: row.last_failed_at,
|
|
105
|
+
lastSkippedAt: row.last_skipped_at,
|
|
106
|
+
ownerAgentId: row.owner_agent_id || 'lead',
|
|
107
|
+
consecutiveFailures: Number(row.consecutive_failures) || 0,
|
|
108
|
+
resultDelivery: row.result_delivery || null,
|
|
94
109
|
nextFireAt: row.next_fire_at,
|
|
95
110
|
deferredUntil: row.deferred_until,
|
|
96
111
|
skippedUntil: row.skipped_until,
|
|
@@ -100,7 +115,7 @@ function rowToDef(row) {
|
|
|
100
115
|
}
|
|
101
116
|
|
|
102
117
|
const COLS =
|
|
103
|
-
'name, description, when_at, when_cron, timezone, target, channel_id,
|
|
118
|
+
'name, description, when_at, when_cron, timezone, target, channel_id, cwd, attachments, delivery, prompt, enabled, status, last_fired_at, last_scheduled_at, last_started_at, last_success_at, last_failed_at, last_skipped_at, owner_agent_id, consecutive_failures, result_delivery, next_fire_at, deferred_until, skipped_until, created_at, updated_at';
|
|
104
119
|
|
|
105
120
|
// ---------------------------------------------------------------------------
|
|
106
121
|
// Public API
|
|
@@ -134,30 +149,28 @@ export async function upsertSchedule(def, { dataDir } = {}) {
|
|
|
134
149
|
def.timezone ?? null,
|
|
135
150
|
def.target,
|
|
136
151
|
def.channelId ?? null,
|
|
137
|
-
def.model ?? null,
|
|
138
152
|
def.cwd ?? null,
|
|
139
|
-
def.workflow ?? null,
|
|
140
153
|
def.attachments ? JSON.stringify(def.attachments) : null,
|
|
141
154
|
def.delivery ?? null,
|
|
142
155
|
def.prompt,
|
|
143
156
|
def.enabled ?? true,
|
|
144
157
|
def.status ?? 'active',
|
|
145
158
|
def.nextFireAt ?? null,
|
|
159
|
+
def.ownerAgentId || 'lead',
|
|
146
160
|
];
|
|
147
161
|
const { rows } = await db.query(
|
|
148
162
|
`INSERT INTO scheduler.schedules
|
|
149
|
-
(name, description, when_at, when_cron, timezone, target, channel_id,
|
|
150
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15
|
|
163
|
+
(name, description, when_at, when_cron, timezone, target, channel_id, cwd, attachments, delivery, prompt, enabled, status, next_fire_at, owner_agent_id)
|
|
164
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15)
|
|
151
165
|
ON CONFLICT (name) DO UPDATE SET
|
|
166
|
+
owner_agent_id = EXCLUDED.owner_agent_id,
|
|
152
167
|
description = EXCLUDED.description,
|
|
153
168
|
when_at = EXCLUDED.when_at,
|
|
154
169
|
when_cron = EXCLUDED.when_cron,
|
|
155
170
|
timezone = EXCLUDED.timezone,
|
|
156
171
|
target = EXCLUDED.target,
|
|
157
172
|
channel_id = EXCLUDED.channel_id,
|
|
158
|
-
model = EXCLUDED.model,
|
|
159
173
|
cwd = EXCLUDED.cwd,
|
|
160
|
-
workflow = EXCLUDED.workflow,
|
|
161
174
|
attachments = EXCLUDED.attachments,
|
|
162
175
|
delivery = EXCLUDED.delivery,
|
|
163
176
|
prompt = EXCLUDED.prompt,
|
|
@@ -173,6 +186,8 @@ export async function upsertSchedule(def, { dataDir } = {}) {
|
|
|
173
186
|
last_started_at = NULL,
|
|
174
187
|
last_success_at = NULL,
|
|
175
188
|
last_failed_at = NULL,
|
|
189
|
+
last_skipped_at = NULL,
|
|
190
|
+
consecutive_failures = 0,
|
|
176
191
|
updated_at = now()
|
|
177
192
|
RETURNING ${COLS}`,
|
|
178
193
|
params
|
|
@@ -198,8 +213,16 @@ async function updateScheduleColumn(name, column, value, dataDir) {
|
|
|
198
213
|
return rowToDef(rows[0]);
|
|
199
214
|
}
|
|
200
215
|
|
|
216
|
+
/** Switching a schedule back on clears its failure streak. */
|
|
201
217
|
export async function setEnabled(name, enabled, { dataDir } = {}) {
|
|
202
|
-
|
|
218
|
+
const db = await getDb(dataDir);
|
|
219
|
+
const { rows } = await db.query(
|
|
220
|
+
`UPDATE scheduler.schedules
|
|
221
|
+
SET enabled = $2, consecutive_failures = CASE WHEN $2 THEN 0 ELSE consecutive_failures END, updated_at = now()
|
|
222
|
+
WHERE name = $1 RETURNING ${COLS}`,
|
|
223
|
+
[name, !!enabled]
|
|
224
|
+
);
|
|
225
|
+
return rowToDef(rows[0]);
|
|
203
226
|
}
|
|
204
227
|
|
|
205
228
|
export async function markFired(name, ts = new Date(), { dataDir } = {}) {
|
|
@@ -240,11 +263,47 @@ export async function claimScheduleRun(name, scheduledAt, startedAt, nextFireAt,
|
|
|
240
263
|
}
|
|
241
264
|
|
|
242
265
|
export async function markScheduleSuccess(name, ts = new Date(), { dataDir } = {}) {
|
|
243
|
-
|
|
266
|
+
const db = await getDb(dataDir);
|
|
267
|
+
const { rows } = await db.query(
|
|
268
|
+
`UPDATE scheduler.schedules SET last_success_at = $2, consecutive_failures = 0, updated_at = now()
|
|
269
|
+
WHERE name = $1 RETURNING ${COLS}`,
|
|
270
|
+
[name, ts]
|
|
271
|
+
);
|
|
272
|
+
return rowToDef(rows[0]);
|
|
244
273
|
}
|
|
245
274
|
|
|
275
|
+
/** One more failure in a row; the one reaching the pause limit switches the
|
|
276
|
+
* schedule off (claimScheduleRun admits only enabled rows). */
|
|
246
277
|
export async function markScheduleFailure(name, ts = new Date(), { dataDir } = {}) {
|
|
247
|
-
|
|
278
|
+
const db = await getDb(dataDir);
|
|
279
|
+
const { rows } = await db.query(
|
|
280
|
+
`UPDATE scheduler.schedules
|
|
281
|
+
SET last_failed_at = $2, consecutive_failures = consecutive_failures + 1,
|
|
282
|
+
enabled = enabled AND consecutive_failures + 1 < $3, updated_at = now()
|
|
283
|
+
WHERE name = $1 RETURNING ${COLS}`,
|
|
284
|
+
[name, ts, ROUTINE_LIMITS.pauseAfterFailures]
|
|
285
|
+
);
|
|
286
|
+
return rowToDef(rows[0]);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Set (`entry`) or clear (null) the undelivered result of run `runId`. */
|
|
290
|
+
export async function recordScheduleDelivery(name, runId, entry, { dataDir } = {}) {
|
|
291
|
+
const db = await getDb(dataDir);
|
|
292
|
+
await db.query(
|
|
293
|
+
entry
|
|
294
|
+
? `UPDATE scheduler.schedules
|
|
295
|
+
SET result_delivery = COALESCE(result_delivery, '{}'::jsonb) || jsonb_build_object($2::text, $3::jsonb)
|
|
296
|
+
WHERE name = $1`
|
|
297
|
+
: `UPDATE scheduler.schedules
|
|
298
|
+
SET result_delivery = NULLIF(COALESCE(result_delivery, '{}'::jsonb) - $2::text, '{}'::jsonb)
|
|
299
|
+
WHERE name = $1`,
|
|
300
|
+
entry ? [name, runId, JSON.stringify(entry)] : [name, runId]
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** A fire skipped because the previous run of the same schedule still runs. */
|
|
305
|
+
export async function markScheduleSkipped(name, ts = new Date(), { dataDir } = {}) {
|
|
306
|
+
return updateScheduleColumn(name, 'last_skipped_at', ts, dataDir);
|
|
248
307
|
}
|
|
249
308
|
|
|
250
309
|
export async function markDone(name, { dataDir } = {}) {
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { logWebhook } from './log.mjs';
|
|
2
|
-
import { updateDeliveryStatus } from '../webhooks-db.mjs';
|
|
2
|
+
import { recordEndpointEvent, updateDeliveryStatus } from '../webhooks-db.mjs';
|
|
3
3
|
import { UNTRUSTED_MARKER, scrubFenceMarker } from './untrusted-fence.mjs';
|
|
4
4
|
|
|
5
5
|
const DISPATCH_TIMEOUT_MS = 10 * 60 * 1000;
|
|
6
6
|
|
|
7
|
+
/** The headers a run's prompt shows (and a stored event keeps). */
|
|
8
|
+
function promptHeaders(headers) {
|
|
9
|
+
return Object.fromEntries(Object.entries(headers || {}).filter(([k]) => k.startsWith('x-') || k === 'content-type'));
|
|
10
|
+
}
|
|
11
|
+
|
|
7
12
|
function buildFencedPayload(body, headers) {
|
|
8
13
|
// Trust boundary: webhook body + headers are external input and must be
|
|
9
14
|
// treated as DATA, never instructions. The directive line gives the
|
|
@@ -11,8 +16,7 @@ function buildFencedPayload(body, headers) {
|
|
|
11
16
|
const _UNTRUSTED = UNTRUSTED_MARKER;
|
|
12
17
|
const payload = scrubFenceMarker(JSON.stringify(body, null, 2));
|
|
13
18
|
const headersSummary = scrubFenceMarker(
|
|
14
|
-
Object.entries(headers)
|
|
15
|
-
.filter(([k]) => k.startsWith('x-') || k === 'content-type')
|
|
19
|
+
Object.entries(promptHeaders(headers))
|
|
16
20
|
.map(([k, v]) => `${k}: ${v}`)
|
|
17
21
|
.join('\n')
|
|
18
22
|
);
|
|
@@ -35,6 +39,14 @@ function webhookRunPrompt(instructions, body, headers) {
|
|
|
35
39
|
return { prompt: `${trusted}\n\n${buildFencedPayload(body, headers)}`, skillSelection: trusted };
|
|
36
40
|
}
|
|
37
41
|
|
|
42
|
+
/** A short label of the event for the routine marker in the bot's chat
|
|
43
|
+
* (display only, never model-visible): the sender's event type and action. */
|
|
44
|
+
function webhookEventSummary(headers, body) {
|
|
45
|
+
const field = (value) => (typeof value === 'string' ? value.trim() : '');
|
|
46
|
+
const type = field(headers?.['x-github-event']) || field(headers?.['sentry-hook-resource']) || field(body?.type);
|
|
47
|
+
return [type, field(body?.action)].filter(Boolean).join(' · ').slice(0, 120);
|
|
48
|
+
}
|
|
49
|
+
|
|
38
50
|
function trackDispatch({ name, deliveryId, dispatchP, controller }) {
|
|
39
51
|
let settled = false;
|
|
40
52
|
const timeoutHandle = setTimeout(
|
|
@@ -63,7 +75,10 @@ function trackDispatch({ name, deliveryId, dispatchP, controller }) {
|
|
|
63
75
|
{ once: true }
|
|
64
76
|
);
|
|
65
77
|
dispatchP.then(
|
|
66
|
-
() =>
|
|
78
|
+
(run) =>
|
|
79
|
+
run?.skipped
|
|
80
|
+
? finish('skipped', {}, `${name}: previous run still in progress; skipped (id=${deliveryId})`)
|
|
81
|
+
: finish('done', {}, `${name}: webhook session run dispatched (id=${deliveryId})`),
|
|
67
82
|
(err) =>
|
|
68
83
|
finish(
|
|
69
84
|
'failed',
|
|
@@ -73,8 +88,8 @@ function trackDispatch({ name, deliveryId, dispatchP, controller }) {
|
|
|
73
88
|
);
|
|
74
89
|
}
|
|
75
90
|
|
|
76
|
-
function createWebhookSessionDispatcher({
|
|
77
|
-
async function dispatchSessionRun(name,
|
|
91
|
+
function createWebhookSessionDispatcher({ getBridgeDispatch }) {
|
|
92
|
+
async function dispatchSessionRun(name, fullPrompt, deliveryId, res, extra = {}) {
|
|
78
93
|
await updateDeliveryStatus(name, deliveryId, 'processing');
|
|
79
94
|
// Session dispatch must not be allowed to hang forever — without a
|
|
80
95
|
// ceiling a stuck LLM call leaves the delivery in `processing`
|
|
@@ -88,13 +103,10 @@ function createWebhookSessionDispatcher({ getConfig, getBridgeDispatch }) {
|
|
|
88
103
|
const controller = new AbortController();
|
|
89
104
|
const dispatchP = Promise.resolve(
|
|
90
105
|
getBridgeDispatch()({
|
|
91
|
-
model: model || null,
|
|
92
106
|
prompt: fullPrompt,
|
|
93
107
|
signal: controller.signal,
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
cwd: extra.cwd || getConfig()?.cwd || null,
|
|
97
|
-
workflow: extra.workflow || null,
|
|
108
|
+
// The endpoint's project folder, named to the bot in the turn.
|
|
109
|
+
cwd: extra.cwd || null,
|
|
98
110
|
attachments: extra.attachments || null,
|
|
99
111
|
delivery: extra.delivery || null,
|
|
100
112
|
// Trusted skill-selection input: only the endpoint-authored
|
|
@@ -104,8 +116,10 @@ function createWebhookSessionDispatcher({ getConfig, getBridgeDispatch }) {
|
|
|
104
116
|
context: {
|
|
105
117
|
source: 'webhook',
|
|
106
118
|
endpoint: name,
|
|
119
|
+
// The bot whose chat the run is a turn in.
|
|
120
|
+
owner: extra.owner || null,
|
|
107
121
|
deliveryId,
|
|
108
|
-
event:
|
|
122
|
+
event: extra.event || null,
|
|
109
123
|
},
|
|
110
124
|
})
|
|
111
125
|
);
|
|
@@ -116,12 +130,18 @@ function createWebhookSessionDispatcher({ getConfig, getBridgeDispatch }) {
|
|
|
116
130
|
|
|
117
131
|
async function dispatchEndpoint({ name, body, headers, res, deliveryId, endpoint }) {
|
|
118
132
|
try {
|
|
119
|
-
const {
|
|
133
|
+
const { instructions, cwd, attachments, delivery, ownerAgentId } = endpoint;
|
|
120
134
|
const run = webhookRunPrompt(instructions, body, headers);
|
|
121
135
|
if (!getBridgeDispatch()) throw new Error(`[webhook] session dispatch requires bridgeDispatch`);
|
|
122
|
-
|
|
136
|
+
// Kept for the app's "Run now", which re-runs the latest event. Losing
|
|
137
|
+
// the record never blocks the delivery itself.
|
|
138
|
+
await recordEndpointEvent(name, { body, headers: promptHeaders(headers) }).catch((e) =>
|
|
139
|
+
logWebhook(`${name}: event record failed: ${e?.message || e}`)
|
|
140
|
+
);
|
|
141
|
+
await dispatchSessionRun(name, run.prompt, deliveryId, res, {
|
|
142
|
+
owner: ownerAgentId || null,
|
|
123
143
|
cwd: cwd || null,
|
|
124
|
-
|
|
144
|
+
event: webhookEventSummary(headers, body),
|
|
125
145
|
attachments: attachments || null,
|
|
126
146
|
delivery: delivery || null,
|
|
127
147
|
skillSelection: run.skillSelection,
|
|
@@ -137,4 +157,4 @@ function createWebhookSessionDispatcher({ getConfig, getBridgeDispatch }) {
|
|
|
137
157
|
return { dispatchEndpoint };
|
|
138
158
|
}
|
|
139
159
|
|
|
140
|
-
export { buildFencedPayload, createWebhookSessionDispatcher, webhookRunPrompt };
|
|
160
|
+
export { buildFencedPayload, createWebhookSessionDispatcher, promptHeaders, webhookEventSummary, webhookRunPrompt };
|