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
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// In-process fan-out of bot changes that reach prompts: a profile edit
|
|
2
|
+
// (PERSONA.md, AGENT.md, name) and a bot created, archived or restored (the
|
|
3
|
+
// roster Lead prompts list). The daemon hosts every session runtime in one
|
|
4
|
+
// process; the change tells each of them, the saving one included, and a
|
|
5
|
+
// runtime whose thread embeds what changed rebuilds its prompt for the next
|
|
6
|
+
// turn (tool-policy-refresh.mjs refreshAgentProfile). A thread no runtime
|
|
7
|
+
// holds catches up when reopened.
|
|
8
|
+
import { invalidateRoleInstructionsCache } from '../runtime/agent/orchestrator/context/role-instructions.mjs';
|
|
9
|
+
|
|
10
|
+
const listeners = new Set();
|
|
11
|
+
|
|
12
|
+
export function subscribeAgentProfileChanges(listener) {
|
|
13
|
+
listeners.add(listener);
|
|
14
|
+
return { unsubscribe: () => listeners.delete(listener) };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Settles once every runtime has applied the edit; one failing runtime never fails the save. */
|
|
18
|
+
export async function publishAgentProfileChange() {
|
|
19
|
+
invalidateRoleInstructionsCache();
|
|
20
|
+
await Promise.allSettled([...listeners].map((listener) => Promise.resolve().then(() => listener())));
|
|
21
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Group-room rows into a room's log session (`sess_room_<id>`,
|
|
3
|
+
* session/agent-room.mjs). The desktop host drives the room and posts each row
|
|
4
|
+
* here; the row is appended to the stored session (never a turn: a room log
|
|
5
|
+
* runs none) and shown at once in a runtime hosting it. A row id is its
|
|
6
|
+
* message id, so a retried post adds nothing. A user row's files are taken in
|
|
7
|
+
* here exactly as a chat prompt's are at submit (session-turns.mjs): the bytes
|
|
8
|
+
* go to the attachment store once, and the row keeps their references.
|
|
9
|
+
*/
|
|
10
|
+
import {
|
|
11
|
+
normalizeRoomRow,
|
|
12
|
+
roomIdOfSession,
|
|
13
|
+
roomMemberOfSession,
|
|
14
|
+
roomRowItem,
|
|
15
|
+
roomRowMessage,
|
|
16
|
+
} from '../runtime/agent/orchestrator/session/agent-room.mjs';
|
|
17
|
+
import { artifactRecords } from '../runtime/shared/handoff-artifacts.mjs';
|
|
18
|
+
import { collectTurnArtifacts, sessionRoots, turnSlice } from './handoff-attachments.mjs';
|
|
19
|
+
import {
|
|
20
|
+
attachmentsForIntake,
|
|
21
|
+
isAttachmentReference,
|
|
22
|
+
materializePromptSubmission,
|
|
23
|
+
preparePromptSubmissionForProvider,
|
|
24
|
+
} from '../runtime/attachments/store.mjs';
|
|
25
|
+
import { promptContentImageMeta } from '../tui/session/queue-helpers.mjs';
|
|
26
|
+
import { agentHomeLiveSurface } from './agent-home-delivery.mjs';
|
|
27
|
+
|
|
28
|
+
/** A user row with its `upload` taken in: the files as stored references
|
|
29
|
+
* (`parts`), their attachment records and the bubble's image chips. */
|
|
30
|
+
async function storedRow(row) {
|
|
31
|
+
const upload = row?.kind === 'user' && row.upload && typeof row.upload === 'object' ? row.upload : null;
|
|
32
|
+
if (!upload) return row;
|
|
33
|
+
const { upload: _upload, ...rest } = row;
|
|
34
|
+
// The words ride along so the composer's text tokens find their pastes.
|
|
35
|
+
const prompt = [{ type: 'text', text: String(row.text ?? '') }, ...(Array.isArray(upload.parts) ? upload.parts : [])];
|
|
36
|
+
const intake = await preparePromptSubmissionForProvider(
|
|
37
|
+
materializePromptSubmission(prompt, {
|
|
38
|
+
...(upload.pastedImages ? { pastedImages: upload.pastedImages } : {}),
|
|
39
|
+
...(upload.pastedTexts ? { pastedTexts: upload.pastedTexts } : {}),
|
|
40
|
+
}),
|
|
41
|
+
// Each member's own intake checks the PDF pages its provider takes.
|
|
42
|
+
{ nativePdf: false }
|
|
43
|
+
);
|
|
44
|
+
return {
|
|
45
|
+
...rest,
|
|
46
|
+
parts: intake.prompt.filter((part) => isAttachmentReference(part) && (part.type !== 'text' || part.textKind)),
|
|
47
|
+
attachments: attachmentsForIntake(intake.prompt, intake.options),
|
|
48
|
+
images: promptContentImageMeta(intake.prompt, intake.options.pastedImages) ?? [],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** A member's speech with the files its turn produced taken in: `artifactsFrom`
|
|
53
|
+
* `{ sessionId, since }` names the member's own session in this room and when
|
|
54
|
+
* its turn started; the files inside that session's folders are stored and
|
|
55
|
+
* ride on the row as references (handoff-attachments.mjs). */
|
|
56
|
+
function speechWithArtifacts(row, roomId, loadSession, log) {
|
|
57
|
+
const from =
|
|
58
|
+
row?.kind === 'speech' && row.artifactsFrom && typeof row.artifactsFrom === 'object' ? row.artifactsFrom : null;
|
|
59
|
+
if (!from) return row;
|
|
60
|
+
const { artifactsFrom: _from, ...rest } = row;
|
|
61
|
+
const member = roomMemberOfSession(String(from.sessionId || ''));
|
|
62
|
+
if (!member || member.roomId !== roomId || member.agentId !== row.speaker) return rest;
|
|
63
|
+
try {
|
|
64
|
+
const session = loadSession(from.sessionId);
|
|
65
|
+
const messages = turnSlice(session?.messages, { since: Number(from.since) || 0 });
|
|
66
|
+
const { files } = collectTurnArtifacts(messages, { cwd: session?.cwd, roots: sessionRoots(session) });
|
|
67
|
+
const stored = files.filter((file) => file.id);
|
|
68
|
+
if (!stored.length) return rest;
|
|
69
|
+
return {
|
|
70
|
+
...rest,
|
|
71
|
+
parts: stored.map((file) => ({
|
|
72
|
+
type: file.kind === 'image' ? 'image' : file.kind === 'text' ? 'text' : 'file',
|
|
73
|
+
attachmentRef: file.id,
|
|
74
|
+
...(file.kind === 'text' ? { textKind: 'file' } : {}),
|
|
75
|
+
mimeType: file.mimeType,
|
|
76
|
+
filename: file.name,
|
|
77
|
+
sizeBytes: file.size,
|
|
78
|
+
})),
|
|
79
|
+
attachments: artifactRecords(stored),
|
|
80
|
+
};
|
|
81
|
+
} catch (error) {
|
|
82
|
+
log(`the files of ${from.sessionId} could not be taken in: ${error?.message || error}`);
|
|
83
|
+
return rest;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function createAgentRoomDelivery({ loadSession, saveSession, live = agentHomeLiveSurface, log = () => {} }) {
|
|
88
|
+
/** Append `rows` to the log of `sessionId`; resolves `{ added, rows }`: how
|
|
89
|
+
* many were new, and every row as stored (files as references, no bytes). */
|
|
90
|
+
async function append(sessionId, rows) {
|
|
91
|
+
const roomId = roomIdOfSession(sessionId);
|
|
92
|
+
if (!roomId) throw new TypeError('not a group-room session');
|
|
93
|
+
const kept = (
|
|
94
|
+
await Promise.all(
|
|
95
|
+
(Array.isArray(rows) ? rows : []).map((row) => storedRow(speechWithArtifacts(row, roomId, loadSession, log)))
|
|
96
|
+
)
|
|
97
|
+
).map((row) => normalizeRoomRow(row));
|
|
98
|
+
const messages = kept.map((row) => roomRowMessage(row));
|
|
99
|
+
const session = loadSession(sessionId);
|
|
100
|
+
if (!session || session.closed === true) throw new Error(`session ${sessionId} is not available`);
|
|
101
|
+
if (!Array.isArray(session.messages)) session.messages = [];
|
|
102
|
+
const posted = new Set(session.messages.map((message) => message?.meta?.transcript?.messageId).filter(Boolean));
|
|
103
|
+
const fresh = messages.filter((message) => !posted.has(message.meta.transcript.messageId));
|
|
104
|
+
if (!fresh.length) return { added: 0, rows: kept };
|
|
105
|
+
session.messages.push(...fresh);
|
|
106
|
+
session.updatedAt = Date.now();
|
|
107
|
+
await saveSession(session);
|
|
108
|
+
for (const message of fresh) {
|
|
109
|
+
const { text, tone, room, messageId, attachments, images } = roomRowItem(message);
|
|
110
|
+
try {
|
|
111
|
+
live()?.notice?.(sessionId, { text, tone, room, messageId, attachments, images });
|
|
112
|
+
} catch (error) {
|
|
113
|
+
log(`live room row in ${sessionId} failed: ${error?.message || error}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return { added: fresh.length, rows: kept };
|
|
117
|
+
}
|
|
118
|
+
return { append };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let processDelivery = null;
|
|
122
|
+
/** Daemon entry: post `rows` into the room log `sessionId`. */
|
|
123
|
+
export async function appendRoomRows(sessionId, rows) {
|
|
124
|
+
if (!processDelivery) {
|
|
125
|
+
const { loadSession, saveSessionAsync } = await import('../runtime/agent/orchestrator/session/store.mjs');
|
|
126
|
+
processDelivery = createAgentRoomDelivery({
|
|
127
|
+
loadSession,
|
|
128
|
+
saveSession: (session) => saveSessionAsync(session, { expectedGeneration: session.generation }),
|
|
129
|
+
log: (line) => process.stderr.write(`mixdog rooms: ${line}\n`),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return processDelivery.append(sessionId, rows);
|
|
133
|
+
}
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `session create`: an attended agent-mode conversation (an agent's home
|
|
3
|
+
* thread) hands coding to a new, visible coding-mode task session — the
|
|
4
|
+
* user's own Lead, created through the same session service and defaults as a
|
|
5
|
+
* desktop New task — and gets each of its own turns' results back.
|
|
6
|
+
*
|
|
7
|
+
* Only turns the origin agent starts (the brief, a later `send` from it)
|
|
8
|
+
* report: when such a turn ends its result is queued durably for the agent's
|
|
9
|
+
* home (agent-home-delivery.mjs), which posts it and wakes the agent once to
|
|
10
|
+
* relay it. A turn blocked on the user's answer or approval posts one faint
|
|
11
|
+
* line instead; the card stays in the task session for the user.
|
|
12
|
+
*
|
|
13
|
+
* An agent's send to any other session reports the same way, by an origin for
|
|
14
|
+
* that send only (reportOrigin). Every reporting turn is also watched durably
|
|
15
|
+
* (agent-home-delivery.mjs `watches`) until its end is queued, so a daemon
|
|
16
|
+
* restart before that re-attaches it (recover): an ended turn reports once
|
|
17
|
+
* from the target's record, a queued or running one is watched to its end.
|
|
18
|
+
*/
|
|
19
|
+
import { randomUUID } from 'node:crypto';
|
|
20
|
+
import { realpathSync, statSync } from 'node:fs';
|
|
21
|
+
import { basename, resolve } from 'node:path';
|
|
22
|
+
import { clean } from '../runtime/agent/orchestrator/runtime-core/session-text.mjs';
|
|
23
|
+
import { collectTurnArtifacts, handoffContent, sessionRoots, turnSlice } from './handoff-attachments.mjs';
|
|
24
|
+
import { roomMemberOfSession } from '../runtime/agent/orchestrator/session/agent-room.mjs';
|
|
25
|
+
|
|
26
|
+
const TITLE_LIMIT = 60;
|
|
27
|
+
|
|
28
|
+
/** `{ cwd, desktopSession, project }` of `project`: a registered Project name
|
|
29
|
+
* (or alias) or an existing directory; none starts a task without a Project
|
|
30
|
+
* in the caller's folder, as a desktop New task does in its task workspace. */
|
|
31
|
+
export function resolveTaskProject(project, { projects = [], cwd = process.cwd() } = {}) {
|
|
32
|
+
const requested = clean(project);
|
|
33
|
+
if (!requested) return { cwd, desktopSession: { classification: 'task', projectPath: null }, project: null };
|
|
34
|
+
const key = requested.toLowerCase();
|
|
35
|
+
const named = projects.filter((entry) => [entry.name, entry.alias].some((name) => clean(name).toLowerCase() === key));
|
|
36
|
+
if (named.length > 1)
|
|
37
|
+
throw new Error(`session: Project "${requested}" is ambiguous; ask the user which one is meant`);
|
|
38
|
+
let path = named[0]?.path || '';
|
|
39
|
+
if (!path) {
|
|
40
|
+
const candidate = resolve(cwd, requested);
|
|
41
|
+
let directory = false;
|
|
42
|
+
try {
|
|
43
|
+
directory = statSync(candidate).isDirectory();
|
|
44
|
+
} catch {}
|
|
45
|
+
if (!directory) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`session: "${requested}" is neither a registered Project nor an existing directory; run list-projects`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
path = realpathSync(candidate);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
cwd: path,
|
|
54
|
+
desktopSession: { classification: 'project', projectPath: path },
|
|
55
|
+
project: { name: named[0]?.name || basename(path), path },
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const turnStatus = (detail) =>
|
|
60
|
+
({ done: 'completed', completed: 'completed', failed: 'failed' })[detail?.status] || 'cancelled';
|
|
61
|
+
|
|
62
|
+
const INTERRUPTED = /^\[Request interrupted/;
|
|
63
|
+
|
|
64
|
+
const messageText = (content) =>
|
|
65
|
+
typeof content === 'string'
|
|
66
|
+
? content
|
|
67
|
+
: (Array.isArray(content) ? content : [])
|
|
68
|
+
.map((part) => (typeof part === 'string' ? part : part?.type === 'text' ? part.text || '' : ''))
|
|
69
|
+
.join('');
|
|
70
|
+
|
|
71
|
+
/** The submissions a stored user message delivered: a turn's prompt
|
|
72
|
+
* (`checkpointId`, `submissionIds` of a merged batch) or a steered message. */
|
|
73
|
+
export function submissionIdsOf(message) {
|
|
74
|
+
if (message?.role !== 'user') return [];
|
|
75
|
+
const meta = message.meta || {};
|
|
76
|
+
const transcript = meta.transcript || {};
|
|
77
|
+
return [
|
|
78
|
+
transcript.checkpointId,
|
|
79
|
+
...(Array.isArray(transcript.submissionIds) ? transcript.submissionIds : []),
|
|
80
|
+
...(Array.isArray(meta.submissionIds) ? meta.submissionIds : []),
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The end of the turn that took submission `runId`, from a session's stored
|
|
85
|
+
* `messages`: `{ status, text }` once that turn recorded its end (its
|
|
86
|
+
* completion, an interruption marker, or the next turn's prompt), 'open'
|
|
87
|
+
* while it has none, null when no turn took it. */
|
|
88
|
+
export function submissionOutcome(messages, runId) {
|
|
89
|
+
const list = Array.isArray(messages) ? messages : [];
|
|
90
|
+
const start = list.findIndex((message) => submissionIdsOf(message).includes(runId));
|
|
91
|
+
if (start < 0) return null;
|
|
92
|
+
for (let index = start; index < list.length; index += 1) {
|
|
93
|
+
const message = list[index];
|
|
94
|
+
const completion = message?.meta?.transcript?.completion;
|
|
95
|
+
if (completion) {
|
|
96
|
+
const status = turnStatus(completion);
|
|
97
|
+
const text = status === 'failed' ? completion.detail || '' : message.role === 'assistant' ? message.content : '';
|
|
98
|
+
return { status, text: messageText(text) };
|
|
99
|
+
}
|
|
100
|
+
if (index === start || message?.role !== 'user') continue;
|
|
101
|
+
if (INTERRUPTED.test(messageText(message.content)) || message.meta?.transcript?.checkpointId) {
|
|
102
|
+
return { status: 'cancelled', text: '' };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return 'open';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const LOST_TEXT = 'The message never ran: the app restarted before its turn started.';
|
|
109
|
+
const GONE_TEXT = 'The session no longer exists.';
|
|
110
|
+
|
|
111
|
+
// The last-used model search stops after this many catalog pages.
|
|
112
|
+
const LAST_USED_ROUTE_MAX_PAGES = 20;
|
|
113
|
+
|
|
114
|
+
export function createAgentTaskSessions({
|
|
115
|
+
getSessionService,
|
|
116
|
+
deliver = async (agentId, report) => (await import('./agent-home-delivery.mjs')).deliverAgentTask(agentId, report),
|
|
117
|
+
// The durable watch of each reporting turn until its end is queued.
|
|
118
|
+
watches = {
|
|
119
|
+
add: async (watch) => (await import('./agent-home-delivery.mjs')).watchAgentTask(watch),
|
|
120
|
+
list: async () => (await import('./agent-home-delivery.mjs')).agentTaskWatches(),
|
|
121
|
+
remove: async (runId) => (await import('./agent-home-delivery.mjs')).unwatchAgentTask(runId),
|
|
122
|
+
},
|
|
123
|
+
// Submissions still queued (durably) for a session after a restart.
|
|
124
|
+
queuedSubmissionIds = async (sessionId) =>
|
|
125
|
+
(await (await import('../tui/session/tui-steering-persist.mjs')).readTuiSteeringPersist(sessionId))
|
|
126
|
+
.map((row) => row?.submissionId)
|
|
127
|
+
.filter(Boolean),
|
|
128
|
+
loadSession = async (sessionId) =>
|
|
129
|
+
(await import('../runtime/agent/orchestrator/session/store.mjs')).loadSession(sessionId),
|
|
130
|
+
listProjects = async () => (await import('../runtime/shared/projects.mjs')).listProjects(),
|
|
131
|
+
newTaskDefault = async () =>
|
|
132
|
+
(await import('../runtime/agent/orchestrator/config.mjs')).loadConfig({ secrets: false }).worktree?.newTaskDefault,
|
|
133
|
+
modelRoute = async (modelRef) => {
|
|
134
|
+
const [{ modelRouteFields, parseScheduleModelRef }, { migrateCodexRoute }, { loadConfig }] = await Promise.all([
|
|
135
|
+
import('../runtime/shared/schedule-model-ref.mjs'),
|
|
136
|
+
import('../runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs'),
|
|
137
|
+
import('../runtime/agent/orchestrator/config.mjs'),
|
|
138
|
+
]);
|
|
139
|
+
// A provider/model[@effort…] ref (a superseded model runs on its
|
|
140
|
+
// successor) or a preset name.
|
|
141
|
+
const ref = parseScheduleModelRef(modelRef);
|
|
142
|
+
if (ref && typeof ref === 'object') return modelRouteFields(migrateCodexRoute(ref));
|
|
143
|
+
const preset = loadConfig({ secrets: false }).presets?.find((p) => p.id === ref || p.name === ref);
|
|
144
|
+
if (!preset) throw new Error(`model "${ref}" is neither a provider/model route nor a known preset name`);
|
|
145
|
+
return modelRouteFields(preset);
|
|
146
|
+
},
|
|
147
|
+
// The Lead (Main) route of the config — what a desktop New task starts on.
|
|
148
|
+
// Null while none is chosen.
|
|
149
|
+
leadRoute = async () => {
|
|
150
|
+
const [{ modelRouteFields }, { resolveRoute }, { loadConfig }] = await Promise.all([
|
|
151
|
+
import('../runtime/shared/schedule-model-ref.mjs'),
|
|
152
|
+
import('./boot/shared.mjs'),
|
|
153
|
+
import('../runtime/agent/orchestrator/config.mjs'),
|
|
154
|
+
]);
|
|
155
|
+
const route = resolveRoute(loadConfig({ secrets: false }), {});
|
|
156
|
+
return clean(route?.provider) && clean(route?.model) ? modelRouteFields(route) : null;
|
|
157
|
+
},
|
|
158
|
+
// The model the user last used in a coding session (the runtime's stand-in
|
|
159
|
+
// for the desktop's last New-task model): the first row that has one in the
|
|
160
|
+
// catalog, which lists newest activity first. Pages on (bounded) until a
|
|
161
|
+
// row qualifies; null when none does.
|
|
162
|
+
lastUsedRoute = async (sessionService) => {
|
|
163
|
+
let cursor = null;
|
|
164
|
+
for (let page = 0; page < LAST_USED_ROUTE_MAX_PAGES; page += 1) {
|
|
165
|
+
const listed = await sessionService.listSessionCatalog({ page: { limit: 50, cursor } });
|
|
166
|
+
const used = (Array.isArray(listed?.sessions) ? listed.sessions : []).find(
|
|
167
|
+
(row) => row?.sessionMode !== 'agent' && clean(row?.provider) && clean(row?.model)
|
|
168
|
+
);
|
|
169
|
+
if (used) return { provider: clean(used.provider), model: clean(used.model) };
|
|
170
|
+
cursor = listed?.nextCursor || null;
|
|
171
|
+
if (!cursor) return null;
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
},
|
|
175
|
+
// The files turn `runId` of `sessionId` produced (handoff-attachments.mjs),
|
|
176
|
+
// read from its live runtime when one is given, else its stored record.
|
|
177
|
+
turnArtifacts = async (sessionId, runId, runtime = null) => {
|
|
178
|
+
const stored = await loadSession(sessionId);
|
|
179
|
+
const live = typeof runtime?.readModelMessages === 'function' ? await runtime.readModelMessages(0) : null;
|
|
180
|
+
const messages = turnSlice(Array.isArray(live?.messages) ? live.messages : stored?.messages, { runId });
|
|
181
|
+
return collectTurnArtifacts(messages, { cwd: stored?.cwd, roots: sessionRoots(stored) });
|
|
182
|
+
},
|
|
183
|
+
log = (line) => process.stderr.write(`mixdog task sessions: ${line}\n`),
|
|
184
|
+
}) {
|
|
185
|
+
/** The report fields of turn `runId`'s files; none when it produced none. */
|
|
186
|
+
const artifactFields = async (sessionId, runId, runtime = null) => {
|
|
187
|
+
try {
|
|
188
|
+
const { files, omitted } = await turnArtifacts(sessionId, runId, runtime);
|
|
189
|
+
return files.length ? { files, ...(omitted ? { omitted } : {}) } : {};
|
|
190
|
+
} catch (error) {
|
|
191
|
+
log(`collecting the files of ${sessionId} failed: ${error?.message || error}`);
|
|
192
|
+
return {};
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
// Marks this process's watches: recovery re-attaches only an earlier one's.
|
|
196
|
+
const processToken = randomUUID();
|
|
197
|
+
const service = () => {
|
|
198
|
+
const value = getSessionService();
|
|
199
|
+
if (!value) throw new Error('session: session service is unavailable');
|
|
200
|
+
return value;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/** Submit `prompt` as the origin agent's turn; its end (and a wait for the
|
|
204
|
+
* user in it) reports to the agent's home. Resolves on intake. */
|
|
205
|
+
async function agentTurn(sessionId, prompt, origin, id = `atask_${randomUUID()}`, beforeReport = null) {
|
|
206
|
+
const sessions = service();
|
|
207
|
+
const target = origin.target === 'session' ? { target: 'session' } : {};
|
|
208
|
+
// The watch is durable before the turn can start, so no end goes
|
|
209
|
+
// unreported across a restart; a turn whose watch could not be stored
|
|
210
|
+
// runs without a report (reportsBack false) rather than claim one.
|
|
211
|
+
let notify = false;
|
|
212
|
+
if (origin.notify !== false) {
|
|
213
|
+
try {
|
|
214
|
+
await watches.add({
|
|
215
|
+
runId: id,
|
|
216
|
+
agentId: origin.agentId,
|
|
217
|
+
homeSessionId: origin.sessionId,
|
|
218
|
+
taskSessionId: sessionId,
|
|
219
|
+
title: origin.title,
|
|
220
|
+
...target,
|
|
221
|
+
...(origin.textOnly ? { textOnly: true } : {}),
|
|
222
|
+
owner: processToken,
|
|
223
|
+
at: Date.now(),
|
|
224
|
+
});
|
|
225
|
+
notify = true;
|
|
226
|
+
} catch (error) {
|
|
227
|
+
log(`watch of ${sessionId} failed, sending without a report: ${error?.message || error}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const dropWatch = () =>
|
|
231
|
+
watches.remove(id).catch((error) => log(`dropping the watch of ${sessionId} failed: ${error?.message || error}`));
|
|
232
|
+
let runtime = null;
|
|
233
|
+
const report = (status, text = '') =>
|
|
234
|
+
Promise.resolve(beforeReport)
|
|
235
|
+
.then(async () =>
|
|
236
|
+
deliver(origin.agentId, {
|
|
237
|
+
runId: `${id}_${status === 'blocked' ? 'blocked' : 'end'}`,
|
|
238
|
+
taskSessionId: sessionId,
|
|
239
|
+
title: origin.title,
|
|
240
|
+
status,
|
|
241
|
+
text,
|
|
242
|
+
...target,
|
|
243
|
+
// A turn's end brings back the files it produced, when the agent
|
|
244
|
+
// may receive them (reportOrigin `textOnly`).
|
|
245
|
+
...(status === 'blocked'
|
|
246
|
+
? {}
|
|
247
|
+
: { watchId: id, ...(origin.textOnly ? {} : await artifactFields(sessionId, id, runtime)) }),
|
|
248
|
+
})
|
|
249
|
+
)
|
|
250
|
+
.catch((error) => log(`report of ${sessionId} failed: ${error?.message || error}`));
|
|
251
|
+
let unwatch = () => {};
|
|
252
|
+
let result;
|
|
253
|
+
try {
|
|
254
|
+
if (notify) {
|
|
255
|
+
runtime = await sessions.materializeSession(sessionId);
|
|
256
|
+
let blocked = false;
|
|
257
|
+
unwatch =
|
|
258
|
+
runtime?.subscribe?.(() => {
|
|
259
|
+
if (blocked || !runtime.getState?.()?.toolApproval) return;
|
|
260
|
+
blocked = true;
|
|
261
|
+
void report('blocked');
|
|
262
|
+
}) || unwatch;
|
|
263
|
+
}
|
|
264
|
+
result = await sessions.submitSession({
|
|
265
|
+
sessionId,
|
|
266
|
+
prompt,
|
|
267
|
+
options: {
|
|
268
|
+
id,
|
|
269
|
+
mode: 'prompt',
|
|
270
|
+
transcriptMeta: { sender: 'lead' },
|
|
271
|
+
...(notify
|
|
272
|
+
? {
|
|
273
|
+
onSettled: (detail) => {
|
|
274
|
+
unwatch();
|
|
275
|
+
void report(turnStatus(detail), detail?.result?.content || detail?.error || '');
|
|
276
|
+
},
|
|
277
|
+
}
|
|
278
|
+
: {}),
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
} catch (error) {
|
|
282
|
+
unwatch();
|
|
283
|
+
if (notify) await dropWatch();
|
|
284
|
+
throw error;
|
|
285
|
+
}
|
|
286
|
+
const accepted = result?.accepted === true;
|
|
287
|
+
if (!accepted) {
|
|
288
|
+
unwatch();
|
|
289
|
+
if (notify) await dropWatch();
|
|
290
|
+
}
|
|
291
|
+
return { accepted, reportsBack: accepted && notify };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Re-attach one watch a stopped daemon left: report the turn's end once
|
|
295
|
+
* from the target's record, or watch it until it ends. */
|
|
296
|
+
async function rearm(watch) {
|
|
297
|
+
const sessionId = watch.taskSessionId;
|
|
298
|
+
const finish = async (status, text = '') =>
|
|
299
|
+
deliver(watch.agentId, {
|
|
300
|
+
runId: `${watch.runId}_end`,
|
|
301
|
+
taskSessionId: sessionId,
|
|
302
|
+
title: watch.title,
|
|
303
|
+
status,
|
|
304
|
+
text,
|
|
305
|
+
...(watch.target === 'session' ? { target: 'session' } : {}),
|
|
306
|
+
watchId: watch.runId,
|
|
307
|
+
...(watch.textOnly || text === GONE_TEXT || text === LOST_TEXT
|
|
308
|
+
? {}
|
|
309
|
+
: await artifactFields(sessionId, watch.runId)),
|
|
310
|
+
});
|
|
311
|
+
if (!(await loadSession(sessionId))) return finish('cancelled', GONE_TEXT);
|
|
312
|
+
const runtime = await service().materializeSession(sessionId);
|
|
313
|
+
let done = false;
|
|
314
|
+
let checking = false;
|
|
315
|
+
let again = false;
|
|
316
|
+
let unsubscribe = () => {};
|
|
317
|
+
const check = async () => {
|
|
318
|
+
if (done) return;
|
|
319
|
+
if (checking) {
|
|
320
|
+
again = true;
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
checking = true;
|
|
324
|
+
try {
|
|
325
|
+
do {
|
|
326
|
+
again = false;
|
|
327
|
+
const outcome = submissionOutcome((await loadSession(sessionId))?.messages, watch.runId);
|
|
328
|
+
const state = runtime?.getState?.() || {};
|
|
329
|
+
let end = outcome && outcome !== 'open' ? outcome : null;
|
|
330
|
+
if (!end && !state.busy) {
|
|
331
|
+
const queued = (state.queued || []).some((entry) => entry?.id === watch.runId);
|
|
332
|
+
if (outcome === 'open' && !queued) end = { status: 'cancelled', text: '' };
|
|
333
|
+
else if (!outcome && !queued && !(await queuedSubmissionIds(sessionId)).includes(watch.runId)) {
|
|
334
|
+
end = { status: 'cancelled', text: LOST_TEXT };
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (end) {
|
|
338
|
+
done = true;
|
|
339
|
+
unsubscribe();
|
|
340
|
+
await finish(end.status, end.text);
|
|
341
|
+
}
|
|
342
|
+
} while (again && !done);
|
|
343
|
+
} finally {
|
|
344
|
+
checking = false;
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
const failed = (error) => log(`re-attaching ${sessionId} failed: ${error?.message || error}`);
|
|
348
|
+
unsubscribe = runtime?.subscribe?.(() => void check().catch(failed)) || unsubscribe;
|
|
349
|
+
await check();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** Daemon start: re-attach every reporting turn an earlier process watched.
|
|
353
|
+
* Resolves once each is reported or watched. */
|
|
354
|
+
async function recover() {
|
|
355
|
+
const pending = (await watches.list()).filter((watch) => watch?.owner !== processToken);
|
|
356
|
+
await Promise.all(
|
|
357
|
+
pending.map((watch) =>
|
|
358
|
+
rearm(watch).catch((error) => log(`re-attaching ${watch?.taskSessionId} failed: ${error?.message || error}`))
|
|
359
|
+
)
|
|
360
|
+
);
|
|
361
|
+
return pending.length;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** Create the task session for `brief` and start it; returns at once. */
|
|
365
|
+
async function create(args = {}, { callerSessionId, callerAgentId, callerCwd, attachmentParts = [] } = {}) {
|
|
366
|
+
if (!callerAgentId) throw new Error('session: create is only for an agent conversation');
|
|
367
|
+
const brief = typeof args.brief === 'string' ? args.brief.trim() : '';
|
|
368
|
+
if (!brief) throw new Error('session: brief is required for action=create');
|
|
369
|
+
const where = resolveTaskProject(args.project, { projects: await listProjects(), cwd: callerCwd || process.cwd() });
|
|
370
|
+
const requestId = `atask_${randomUUID()}`;
|
|
371
|
+
const title = (clean(args.title) || clean(brief.split('\n')[0])).slice(0, TITLE_LIMIT);
|
|
372
|
+
const taskOrigin = {
|
|
373
|
+
agentId: callerAgentId,
|
|
374
|
+
sessionId: callerSessionId,
|
|
375
|
+
requestId,
|
|
376
|
+
title,
|
|
377
|
+
notify: args.notify !== false,
|
|
378
|
+
};
|
|
379
|
+
// A task session runs in coding mode under Lead: an explicit model wins,
|
|
380
|
+
// else the Lead route, else the last-used model. Never a model-less
|
|
381
|
+
// session — the composer would show no model and its first turn would fail.
|
|
382
|
+
const route = clean(args.model)
|
|
383
|
+
? await modelRoute(clean(args.model))
|
|
384
|
+
: (await leadRoute()) || (await lastUsedRoute(service()));
|
|
385
|
+
if (!route) throw new Error('session: no model to start the task session on. Choose a Lead model first.');
|
|
386
|
+
const sessions = service();
|
|
387
|
+
const created = await sessions.createSession({
|
|
388
|
+
...route,
|
|
389
|
+
cwd: where.cwd,
|
|
390
|
+
desktopSession: where.desktopSession,
|
|
391
|
+
sessionProfile: { taskOrigin },
|
|
392
|
+
});
|
|
393
|
+
const sessionId = String(created?.sessionId || '');
|
|
394
|
+
if (!sessionId) throw new Error('session: task session creation returned no session id');
|
|
395
|
+
// The user's New task default: a Project task starts in its own worktree.
|
|
396
|
+
if (where.project && (await newTaskDefault()) === 'worktree') {
|
|
397
|
+
try {
|
|
398
|
+
await sessions.configureSession(
|
|
399
|
+
{
|
|
400
|
+
sessionId,
|
|
401
|
+
action: 'requestWorktree',
|
|
402
|
+
args: [{ provision: true, carryChanges: 'leave', prompt: title, txId: requestId }],
|
|
403
|
+
},
|
|
404
|
+
{ revision: 1 }
|
|
405
|
+
);
|
|
406
|
+
} catch (error) {
|
|
407
|
+
if (error?.code !== 'WORKTREE_UNSUPPORTED_REPO') throw error;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// "Started" lands in the home before any report of the brief's turn.
|
|
411
|
+
let started = () => {};
|
|
412
|
+
const ready = new Promise((resolveReady) => {
|
|
413
|
+
started = resolveReady;
|
|
414
|
+
});
|
|
415
|
+
// The brief carries the files the agent forwarded (workspace-tools.mjs).
|
|
416
|
+
const prompt = handoffContent(brief, attachmentParts);
|
|
417
|
+
const { accepted, reportsBack } = await agentTurn(sessionId, prompt, taskOrigin, requestId, ready);
|
|
418
|
+
if (!accepted) throw new Error(`session: task session ${sessionId} did not accept the brief`);
|
|
419
|
+
if (reportsBack) {
|
|
420
|
+
await deliver(callerAgentId, {
|
|
421
|
+
runId: `${requestId}_started`,
|
|
422
|
+
taskSessionId: sessionId,
|
|
423
|
+
title,
|
|
424
|
+
status: 'started',
|
|
425
|
+
}).catch((error) => log(`start line of ${sessionId} failed: ${error?.message || error}`));
|
|
426
|
+
}
|
|
427
|
+
started();
|
|
428
|
+
return {
|
|
429
|
+
session_id: sessionId,
|
|
430
|
+
title,
|
|
431
|
+
project: where.project?.path ?? null,
|
|
432
|
+
...(taskOrigin.notify && !reportsBack ? { reportsBack: false } : {}),
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** The origin a `send` of agent `agentId` (from its home `callerSessionId`)
|
|
437
|
+
* reports to: the stored one of a task session it created, else one for
|
|
438
|
+
* this send only — never stored on the target, whose own origin and owner
|
|
439
|
+
* stay as they are. Null for a caller that is no agent conversation. */
|
|
440
|
+
async function reportOrigin(sessionId, agentId, callerSessionId) {
|
|
441
|
+
if (!agentId) return null;
|
|
442
|
+
const target = await loadSession(sessionId);
|
|
443
|
+
const origin = target?.taskOrigin;
|
|
444
|
+
if (origin?.agentId === agentId) return origin;
|
|
445
|
+
const title = (clean(target?.title) || sessionId).slice(0, TITLE_LIMIT);
|
|
446
|
+
// Files come back only from sessions the agent created (above) or its own
|
|
447
|
+
// room member threads; any other session reports its text alone.
|
|
448
|
+
const own = roomMemberOfSession(sessionId)?.agentId === agentId;
|
|
449
|
+
return {
|
|
450
|
+
agentId,
|
|
451
|
+
sessionId: callerSessionId,
|
|
452
|
+
title,
|
|
453
|
+
notify: true,
|
|
454
|
+
target: 'session',
|
|
455
|
+
...(own ? {} : { textOnly: true }),
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return { create, reportOrigin, recover, send: (sessionId, prompt, origin) => agentTurn(sessionId, prompt, origin) };
|
|
460
|
+
}
|