mixdog 1.2.6 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSES/simple-icons-CC0-1.0.txt +30 -0
- package/NOTICE.md +19 -0
- 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 +7 -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 +11 -4
- package/src/defaults/skills/setup-guide/SKILL.md +175 -0
- package/src/defaults/skills/setup-guide/model-recommendations.json +333 -0
- package/src/lib/rules-builder.cjs +55 -6
- 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 +21 -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 +182 -13
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +4 -2
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +215 -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-login.mjs +10 -3
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- 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/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 +12 -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 +10 -4
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +4 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +50 -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 +162 -0
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +641 -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 +165 -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/pre-dispatch-deny.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +16 -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 +7 -0
- 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/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 +27 -5
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +55 -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/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 +24 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +32 -5
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +70 -0
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +277 -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 +44 -1
- 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/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/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-route-config.d.mts +3 -0
- package/src/runtime/shared/agent-route-config.mjs +16 -0
- package/src/runtime/shared/atomic-file.mjs +14 -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 +52 -0
- package/src/runtime/shared/connector-catalog.mjs +230 -0
- package/src/runtime/shared/connector-triggers.d.mts +3 -0
- package/src/runtime/shared/connector-triggers.mjs +43 -0
- package/src/runtime/shared/google-oauth-client.mjs +24 -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/plain-preview.d.mts +1 -0
- package/src/runtime/shared/plain-preview.mjs +28 -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/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 +56 -0
- package/src/runtime/shared/user-data-guard.mjs +114 -36
- package/src/session-runtime/agent-home-delivery.mjs +459 -0
- package/src/session-runtime/agent-profile-changes.mjs +21 -0
- package/src/session-runtime/agent-room-delivery.mjs +92 -0
- package/src/session-runtime/agent-task-sessions.mjs +200 -0
- package/src/session-runtime/boot/apis.mjs +50 -18
- package/src/session-runtime/boot/facade.mjs +5 -8
- package/src/session-runtime/boot/infrastructure.mjs +28 -17
- package/src/session-runtime/boot/tools.mjs +3 -0
- package/src/session-runtime/channel-config-api.mjs +53 -5
- 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/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 +31 -2
- 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/set-route.mjs +14 -3
- 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/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 +10 -0
- 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/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 +68 -6
- package/src/session-runtime/setup-tool/extended-actions.mjs +72 -14
- package/src/session-runtime/setup-tool/settings-contract.mjs +8 -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 +238 -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 +3 -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 +556 -22
- 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 +11 -2
- package/src/standalone/daemon.mjs +18 -4
- package/src/standalone/session-protocol.mjs +27 -0
- package/src/standalone/session-service/session-calls/session-turns.mjs +9 -0
- package/src/standalone/session-service.mjs +19 -0
- package/src/tui/app/slash-commands.mjs +20 -0
- package/src/tui/dist/index.mjs +164 -86
- package/src/tui/session/context-state.mjs +6 -1
- package/src/tui/session/notice-surface.mjs +35 -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 +48 -3
- 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 +6 -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 +56 -0
- package/src/tui/session/turn.mjs +22 -6
- package/src/session-runtime/automation-agents.mjs +0 -94
- package/src/session-runtime/automation-workflow.mjs +0 -59
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rows posted into an agent's home thread (`sess_home_<owner>`) from outside
|
|
3
|
+
* its turns: held requests of unattended runs, reports of task sessions the
|
|
4
|
+
* agent created, and routine results left by runs from before routines ran as
|
|
5
|
+
* turns in the thread itself (routine-home-turn.mjs).
|
|
6
|
+
*
|
|
7
|
+
* Delivery is durable. The finished run is first ENQUEUED in a persisted
|
|
8
|
+
* per-home queue (`routine-results.json`), keyed by the run id, so a retried
|
|
9
|
+
* enqueue adds nothing and a crash between posting and dequeuing never posts
|
|
10
|
+
* twice (the posted message id is derived from the run id). One drainer per
|
|
11
|
+
* home appends every queued result FIFO once the thread is idle; posting never
|
|
12
|
+
* starts a turn. A home thread that does not exist yet keeps its results
|
|
13
|
+
* queued until it does; a deleted owner's results move to Mixy's home. Each
|
|
14
|
+
* home keeps at most ROUTINE_LIMITS.homeQueueLimit results (the oldest drop
|
|
15
|
+
* behind one "n omitted" notice), and posted run ids stay in a ledger so a
|
|
16
|
+
* replay never posts twice, even after the thread was reset. The daemon
|
|
17
|
+
* re-drains every queue on start, and
|
|
18
|
+
* registers the live surface so a thread open in a window shows the notice at
|
|
19
|
+
* once; a cold thread picks it up from its stored record.
|
|
20
|
+
*/
|
|
21
|
+
import { readFile } from 'node:fs/promises';
|
|
22
|
+
import { join } from 'node:path';
|
|
23
|
+
import {
|
|
24
|
+
MIXY_AGENT_ID,
|
|
25
|
+
agentHomeSessionId,
|
|
26
|
+
agentIdOfHomeSession,
|
|
27
|
+
} from '../runtime/agent/orchestrator/session/agent-home.mjs';
|
|
28
|
+
import {
|
|
29
|
+
agentTaskMessage,
|
|
30
|
+
agentTaskWakePrompt,
|
|
31
|
+
routineOmittedMessage,
|
|
32
|
+
routineResultMessage,
|
|
33
|
+
routineResultNotice,
|
|
34
|
+
routineRunNotice,
|
|
35
|
+
routineSummary,
|
|
36
|
+
} from '../runtime/agent/orchestrator/session/routine-result.mjs';
|
|
37
|
+
import { parseJsonText, updateJsonAtomic } from '../runtime/shared/atomic-file.mjs';
|
|
38
|
+
import { ROUTINE_LIMITS } from '../runtime/shared/routine-limits.mjs';
|
|
39
|
+
import {
|
|
40
|
+
heldRequestMessage,
|
|
41
|
+
heldRequestNotice,
|
|
42
|
+
} from '../runtime/agent/orchestrator/session/human-gate/held-message.mjs';
|
|
43
|
+
|
|
44
|
+
/** A queued item's stored message and live notice: a routine result, or a
|
|
45
|
+
* held request of an unattended run (session-runtime/held-requests.mjs). */
|
|
46
|
+
function deliveredRow(item, messageId) {
|
|
47
|
+
if (item.kind === 'held-request') {
|
|
48
|
+
const message = heldRequestMessage({ ...item.held, at: item.at, messageId });
|
|
49
|
+
return { message, notice: heldRequestNotice(message) };
|
|
50
|
+
}
|
|
51
|
+
if (item.kind === 'agent-task') {
|
|
52
|
+
// A turn's end (not its start or a wait for the user) wakes the agent.
|
|
53
|
+
const message = agentTaskMessage({ ...item, messageId });
|
|
54
|
+
return { message, notice: routineResultNotice(message), wake: item.roundTrip === true };
|
|
55
|
+
}
|
|
56
|
+
const message = routineResultMessage({ ...item, messageId });
|
|
57
|
+
return { message, notice: routineResultNotice(message) };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The agent a schedule belongs to; ownerless rows belong to Mixy. */
|
|
61
|
+
export function scheduleOwnerAgentId(schedule) {
|
|
62
|
+
const owner = typeof schedule?.ownerAgentId === 'string' ? schedule.ownerAgentId.trim() : '';
|
|
63
|
+
return owner && agentHomeSessionId(owner) ? owner : MIXY_AGENT_ID;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let liveSurface = null;
|
|
67
|
+
/** `{ busy(sessionId), notice(sessionId, notice), wake(sessionId, prompt),
|
|
68
|
+
* submit(sessionId, prompt, options), abort(sessionId) }` from the session
|
|
69
|
+
* service; `wake` starts one hidden turn, `submit` queues a routine turn
|
|
70
|
+
* (resolves whether it was accepted), `abort` stops the running turn. */
|
|
71
|
+
export function setAgentHomeLiveSurface(surface) {
|
|
72
|
+
liveSurface = surface || null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The registered live surface (group-room rows post through it too). */
|
|
76
|
+
export function agentHomeLiveSurface() {
|
|
77
|
+
return liveSurface;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Run ids already posted, kept per home so a replayed run never posts twice,
|
|
81
|
+
// even into a thread that was reset ("대화 새로 시작") since.
|
|
82
|
+
const DELIVERED_LEDGER = 500;
|
|
83
|
+
|
|
84
|
+
/** The persisted queue at `filePath`: `{ homes: { [homeSessionId]: item[] },
|
|
85
|
+
* omitted: { [homeSessionId]: { id, count } }, delivered: { [homeSessionId]:
|
|
86
|
+
* runId[] } }`. A home keeps at most `limit` items: past it the oldest drop,
|
|
87
|
+
* counted under `omitted` so the thread gets one "n omitted" notice. */
|
|
88
|
+
export function createRoutineResultQueue(filePath, { limit = ROUTINE_LIMITS.homeQueueLimit } = {}) {
|
|
89
|
+
const section = (doc, key) => (doc && typeof doc[key] === 'object' && doc[key]) || {};
|
|
90
|
+
const parts = (doc) => ({
|
|
91
|
+
homes: section(doc, 'homes'),
|
|
92
|
+
omitted: section(doc, 'omitted'),
|
|
93
|
+
delivered: section(doc, 'delivered'),
|
|
94
|
+
// Agent-initiated round trips per task session (session create).
|
|
95
|
+
rounds: section(doc, 'rounds'),
|
|
96
|
+
});
|
|
97
|
+
async function read() {
|
|
98
|
+
try {
|
|
99
|
+
return parts(parseJsonText(await readFile(filePath, 'utf8')));
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (error?.code === 'ENOENT') return parts(null);
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/** `doc` with `items` appended to `homeId`'s list, minus run ids it holds or posted. */
|
|
106
|
+
function appended(doc, homeId, items, omittedCount = 0) {
|
|
107
|
+
const list = doc.homes[homeId] || [];
|
|
108
|
+
const seen = new Set([...list.map((row) => row.runId), ...(doc.delivered[homeId] || [])]);
|
|
109
|
+
const fresh = items.filter((item) => !seen.has(item.runId) && seen.add(item.runId));
|
|
110
|
+
if (!fresh.length && !omittedCount) return null;
|
|
111
|
+
const next = [...list, ...fresh];
|
|
112
|
+
const drop = Math.max(0, next.length - limit);
|
|
113
|
+
const omitted = { ...doc.omitted };
|
|
114
|
+
const count = drop + omittedCount;
|
|
115
|
+
if (count) {
|
|
116
|
+
const previous = omitted[homeId];
|
|
117
|
+
omitted[homeId] = { id: previous?.id || `omit_${next[0].runId}`, count: (previous?.count || 0) + count };
|
|
118
|
+
}
|
|
119
|
+
return { ...doc, homes: { ...doc.homes, [homeId]: next.slice(drop) }, omitted };
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
async pendingHomes() {
|
|
123
|
+
const { homes, omitted } = await read();
|
|
124
|
+
return [...new Set([...Object.keys(homes).filter((id) => homes[id]?.length), ...Object.keys(omitted)])];
|
|
125
|
+
},
|
|
126
|
+
async items(homeId) {
|
|
127
|
+
return (await read()).homes[homeId] || [];
|
|
128
|
+
},
|
|
129
|
+
/** `{ id, count }` of the results dropped for `homeId`; null when none. */
|
|
130
|
+
async omitted(homeId) {
|
|
131
|
+
return (await read()).omitted[homeId] || null;
|
|
132
|
+
},
|
|
133
|
+
async add(homeId, item) {
|
|
134
|
+
await updateJsonAtomic(filePath, (raw) => {
|
|
135
|
+
const doc = parts(raw);
|
|
136
|
+
if (item.roundTrip !== true) return appended(doc, homeId, [item]) ?? undefined;
|
|
137
|
+
// A task turn's end counts one round trip, once per run id.
|
|
138
|
+
const round = (doc.rounds[item.taskSessionId] || 0) + 1;
|
|
139
|
+
const next = appended(doc, homeId, [{ ...item, round }]);
|
|
140
|
+
return next ? { ...next, rounds: { ...doc.rounds, [item.taskSessionId]: round } } : undefined;
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
/** Hand every queued item (and omission) of `fromHome` to `toHome`. */
|
|
144
|
+
async move(fromHome, toHome) {
|
|
145
|
+
await updateJsonAtomic(filePath, (raw) => {
|
|
146
|
+
const doc = parts(raw);
|
|
147
|
+
const items = doc.homes[fromHome] || [];
|
|
148
|
+
const dropped = doc.omitted[fromHome]?.count || 0;
|
|
149
|
+
const next = appended(doc, toHome, items, dropped) || doc;
|
|
150
|
+
const homes = { ...next.homes };
|
|
151
|
+
const omitted = { ...next.omitted };
|
|
152
|
+
delete homes[fromHome];
|
|
153
|
+
delete omitted[fromHome];
|
|
154
|
+
return { ...next, homes, omitted };
|
|
155
|
+
});
|
|
156
|
+
},
|
|
157
|
+
/** Dequeue the posted `runIds` (and the posted omission notice `omitted`). */
|
|
158
|
+
async remove(homeId, runIds, omitted = null) {
|
|
159
|
+
await updateJsonAtomic(filePath, (raw) => {
|
|
160
|
+
const doc = parts(raw);
|
|
161
|
+
const homes = { ...doc.homes };
|
|
162
|
+
const rest = (homes[homeId] || []).filter((row) => !runIds.includes(row.runId));
|
|
163
|
+
if (rest.length) homes[homeId] = rest;
|
|
164
|
+
else delete homes[homeId];
|
|
165
|
+
const delivered = { ...doc.delivered };
|
|
166
|
+
if (runIds.length) delivered[homeId] = [...(delivered[homeId] || []), ...runIds].slice(-DELIVERED_LEDGER);
|
|
167
|
+
const omittedNow = { ...doc.omitted };
|
|
168
|
+
const current = omittedNow[homeId];
|
|
169
|
+
if (omitted && current?.id === omitted.id) {
|
|
170
|
+
// More dropped while this pass posted: they get a notice of their own.
|
|
171
|
+
const left = current.count - omitted.count;
|
|
172
|
+
if (left > 0) omittedNow[homeId] = { id: `${current.id}.${omitted.count}`, count: left };
|
|
173
|
+
else delete omittedNow[homeId];
|
|
174
|
+
}
|
|
175
|
+
return { ...doc, homes, omitted: omittedNow, delivered };
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const messageIdOf = (runId) => `routine_${runId}`;
|
|
182
|
+
|
|
183
|
+
export function createAgentHomeDelivery({
|
|
184
|
+
loadSession,
|
|
185
|
+
saveSession,
|
|
186
|
+
turnInFlight,
|
|
187
|
+
queue,
|
|
188
|
+
live = () => liveSurface,
|
|
189
|
+
retryLater = (fn, ms) => setTimeout(fn, ms).unref?.(),
|
|
190
|
+
busyRetryMs = 1000,
|
|
191
|
+
missingRetryMs = 60_000,
|
|
192
|
+
// 'present' | 'deleted' | 'unknown' per agent id: a deleted owner's results go to Mixy.
|
|
193
|
+
agentPresence = () => 'unknown',
|
|
194
|
+
log = (line) => process.stderr.write(`mixdog routines: ${line}\n`),
|
|
195
|
+
}) {
|
|
196
|
+
const busy = (sessionId) => {
|
|
197
|
+
if (turnInFlight(sessionId)) return true;
|
|
198
|
+
try {
|
|
199
|
+
return live()?.busy?.(sessionId) === true;
|
|
200
|
+
} catch {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
const draining = new Map(); // home → the running pass
|
|
205
|
+
const again = new Set(); // homes enqueued to while their pass ran
|
|
206
|
+
const waiting = new Set(); // homes with a retry timer armed
|
|
207
|
+
const shownLive = new Set(); // held-request message ids shown live in a busy thread
|
|
208
|
+
|
|
209
|
+
/** A held request waits on the user, possibly raised by the very turn that
|
|
210
|
+
* keeps its thread busy (a routine turn): show it live at once; the stored
|
|
211
|
+
* row (same messageId) replaces it once the thread is idle. */
|
|
212
|
+
function showHeldLive(homeId, items) {
|
|
213
|
+
for (const item of items) {
|
|
214
|
+
if (item.kind !== 'held-request') continue;
|
|
215
|
+
const messageId = messageIdOf(item.runId);
|
|
216
|
+
if (shownLive.has(messageId)) continue;
|
|
217
|
+
shownLive.add(messageId);
|
|
218
|
+
try {
|
|
219
|
+
live()?.notice?.(homeId, { ...deliveredRow(item, messageId).notice, messageId });
|
|
220
|
+
} catch (error) {
|
|
221
|
+
log(`live notice in ${homeId} failed: ${error?.message || error}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function later(homeId, ms) {
|
|
227
|
+
if (waiting.has(homeId)) return;
|
|
228
|
+
waiting.add(homeId);
|
|
229
|
+
retryLater(() => {
|
|
230
|
+
waiting.delete(homeId);
|
|
231
|
+
void drain(homeId);
|
|
232
|
+
}, ms);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Post every queued result of `homeId` if its thread is idle; else retry later. */
|
|
236
|
+
async function drainOnce(homeId) {
|
|
237
|
+
const items = await queue.items(homeId);
|
|
238
|
+
const omitted = await queue.omitted(homeId);
|
|
239
|
+
if (!items.length && !omitted) return;
|
|
240
|
+
const agentId = agentIdOfHomeSession(homeId);
|
|
241
|
+
if (agentId && agentId !== MIXY_AGENT_ID && agentPresence(agentId) === 'deleted') {
|
|
242
|
+
// The owner is gone (its home archived): Mixy, the default owner, gets them.
|
|
243
|
+
const mixyHome = agentHomeSessionId(MIXY_AGENT_ID);
|
|
244
|
+
log(`${items.length} result(s) of the deleted agent ${agentId} go to ${mixyHome}`);
|
|
245
|
+
await queue.move(homeId, mixyHome);
|
|
246
|
+
void drain(mixyHome);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (busy(homeId)) {
|
|
250
|
+
showHeldLive(homeId, items);
|
|
251
|
+
return later(homeId, busyRetryMs);
|
|
252
|
+
}
|
|
253
|
+
// From here to the push nothing awaits, so no turn can start in between.
|
|
254
|
+
const session = loadSession(homeId);
|
|
255
|
+
if (!session || session.closed === true) {
|
|
256
|
+
log(`${items.length} result(s) wait for the home thread ${homeId}`);
|
|
257
|
+
return later(homeId, missingRetryMs);
|
|
258
|
+
}
|
|
259
|
+
if (!Array.isArray(session.messages)) session.messages = [];
|
|
260
|
+
const posted = new Set(session.messages.map((message) => message?.meta?.transcript?.messageId).filter(Boolean));
|
|
261
|
+
const fresh = [];
|
|
262
|
+
const omittedId = omitted ? messageIdOf(omitted.id) : '';
|
|
263
|
+
if (omitted && !posted.has(omittedId)) {
|
|
264
|
+
const message = routineOmittedMessage({ count: omitted.count, messageId: omittedId });
|
|
265
|
+
session.messages.push(message);
|
|
266
|
+
fresh.push(message);
|
|
267
|
+
}
|
|
268
|
+
const woken = [];
|
|
269
|
+
for (const item of items) {
|
|
270
|
+
const row = deliveredRow(item, messageIdOf(item.runId));
|
|
271
|
+
// Every dequeued task result wakes once, posted in this pass or before.
|
|
272
|
+
if (row.wake) woken.push(row.message);
|
|
273
|
+
if (posted.has(messageIdOf(item.runId))) continue;
|
|
274
|
+
session.messages.push(row.message);
|
|
275
|
+
fresh.push(row);
|
|
276
|
+
}
|
|
277
|
+
for (const item of items) shownLive.delete(messageIdOf(item.runId));
|
|
278
|
+
if (fresh.length) {
|
|
279
|
+
session.updatedAt = Date.now();
|
|
280
|
+
await saveSession(session);
|
|
281
|
+
for (const { message, notice } of fresh) {
|
|
282
|
+
try {
|
|
283
|
+
live()?.notice?.(homeId, { ...notice, messageId: message.meta.transcript.messageId });
|
|
284
|
+
} catch (error) {
|
|
285
|
+
log(`live notice in ${homeId} failed: ${error?.message || error}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
await queue.remove(
|
|
290
|
+
homeId,
|
|
291
|
+
items.map((item) => item.runId),
|
|
292
|
+
omitted
|
|
293
|
+
);
|
|
294
|
+
// Dequeued task results wake the agent with ONE hidden turn to relay them;
|
|
295
|
+
// a replayed run id is never queued again, so it wakes nobody.
|
|
296
|
+
if (woken.length) {
|
|
297
|
+
try {
|
|
298
|
+
await live()?.wake?.(homeId, agentTaskWakePrompt(woken));
|
|
299
|
+
} catch (error) {
|
|
300
|
+
log(`waking ${homeId} failed: ${error?.message || error}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** The single drainer of `homeId`; a call during a pass runs one more pass. */
|
|
306
|
+
function drain(homeId) {
|
|
307
|
+
const running = draining.get(homeId);
|
|
308
|
+
if (running) {
|
|
309
|
+
again.add(homeId);
|
|
310
|
+
return running;
|
|
311
|
+
}
|
|
312
|
+
const pass = (async () => {
|
|
313
|
+
try {
|
|
314
|
+
do {
|
|
315
|
+
again.delete(homeId);
|
|
316
|
+
await drainOnce(homeId);
|
|
317
|
+
} while (again.has(homeId));
|
|
318
|
+
} catch (error) {
|
|
319
|
+
log(`delivery into ${homeId} failed, retrying: ${error?.message || error}`);
|
|
320
|
+
later(homeId, busyRetryMs);
|
|
321
|
+
} finally {
|
|
322
|
+
draining.delete(homeId);
|
|
323
|
+
}
|
|
324
|
+
})();
|
|
325
|
+
draining.set(homeId, pass);
|
|
326
|
+
return pass;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** Persist `result` ({ runId, kind, ok, text, runSessionId }) of a schedule
|
|
330
|
+
* or webhook `row` for its owner's home, then start draining it. Resolves
|
|
331
|
+
* once it is queued. */
|
|
332
|
+
async function enqueue(row, result) {
|
|
333
|
+
const homeId = agentHomeSessionId(scheduleOwnerAgentId(row));
|
|
334
|
+
if (!result?.runId) throw new Error('routine result: runId required');
|
|
335
|
+
await queue.add(homeId, {
|
|
336
|
+
runId: String(result.runId),
|
|
337
|
+
kind: result.kind === 'webhook' ? 'webhook' : 'schedule',
|
|
338
|
+
name: row.name,
|
|
339
|
+
ok: result.ok === true,
|
|
340
|
+
text: routineSummary(result.text),
|
|
341
|
+
runSessionId: result.runSessionId || null,
|
|
342
|
+
...(result.reason ? { reason: String(result.reason) } : {}),
|
|
343
|
+
...(result.paused === true ? { paused: true } : {}),
|
|
344
|
+
at: Date.now(),
|
|
345
|
+
});
|
|
346
|
+
void drain(homeId);
|
|
347
|
+
return homeId;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** Persist one held-request row (`held`: normalizeHeldRequest fields) for
|
|
351
|
+
* `agentId`'s home thread, then start draining it. Each status posts once. */
|
|
352
|
+
async function enqueueHeld(agentId, held) {
|
|
353
|
+
const homeId = agentHomeSessionId(agentId) || agentHomeSessionId(MIXY_AGENT_ID);
|
|
354
|
+
if (!held?.id) throw new Error('held request: id required');
|
|
355
|
+
await queue.add(homeId, {
|
|
356
|
+
runId: `${held.id}_${held.status || 'pending'}`,
|
|
357
|
+
kind: 'held-request',
|
|
358
|
+
held,
|
|
359
|
+
at: Date.now(),
|
|
360
|
+
});
|
|
361
|
+
void drain(homeId);
|
|
362
|
+
return homeId;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** Persist one report of a coding task session `agentId` created (session
|
|
366
|
+
* create) for its home thread, then start draining it. `status` 'started'
|
|
367
|
+
* and 'blocked' only post a line; a turn's end also wakes the agent. */
|
|
368
|
+
async function enqueueTask(agentId, { runId, taskSessionId, title, status, text = '' }) {
|
|
369
|
+
const homeId = agentHomeSessionId(agentId) || agentHomeSessionId(MIXY_AGENT_ID);
|
|
370
|
+
if (!runId || !taskSessionId) throw new Error('task report: runId and taskSessionId required');
|
|
371
|
+
await queue.add(homeId, {
|
|
372
|
+
runId: String(runId),
|
|
373
|
+
kind: 'agent-task',
|
|
374
|
+
taskSessionId,
|
|
375
|
+
title,
|
|
376
|
+
status,
|
|
377
|
+
text: routineSummary(text),
|
|
378
|
+
roundTrip: status !== 'started' && status !== 'blocked',
|
|
379
|
+
at: Date.now(),
|
|
380
|
+
});
|
|
381
|
+
void drain(homeId);
|
|
382
|
+
return homeId;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/** Show `run`'s live-only row (routineRunNotice: `running`, or a result whose
|
|
386
|
+
* delivery failed) in `row`'s owner home when it is open and idle. Nothing
|
|
387
|
+
* is stored and no turn or unread mark follows; the posted result (same
|
|
388
|
+
* messageId) replaces it. False when no window shows it. */
|
|
389
|
+
function announce(row, run) {
|
|
390
|
+
const homeId = agentHomeSessionId(scheduleOwnerAgentId(row));
|
|
391
|
+
if (!run?.runId || busy(homeId)) return false;
|
|
392
|
+
const notice = routineRunNotice({ ...run, name: row.name, messageId: messageIdOf(run.runId) });
|
|
393
|
+
try {
|
|
394
|
+
return live()?.notice?.(homeId, notice) === true;
|
|
395
|
+
} catch (error) {
|
|
396
|
+
log(`live notice in ${homeId} failed: ${error?.message || error}`);
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** Drain every queue left by an earlier process. */
|
|
402
|
+
async function recover() {
|
|
403
|
+
const homes = await queue.pendingHomes();
|
|
404
|
+
for (const homeId of homes) void drain(homeId);
|
|
405
|
+
return homes;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return { enqueue, enqueueHeld, enqueueTask, announce, drain, recover };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
let defaultDelivery = null;
|
|
412
|
+
/** The process delivery over the session store and data dir; built on first use. */
|
|
413
|
+
async function processDelivery() {
|
|
414
|
+
if (!defaultDelivery) {
|
|
415
|
+
const [{ loadSession, saveSessionAsync }, { isSessionTurnInFlight }, { getPluginData }, { createAgentEditorApi }] =
|
|
416
|
+
await Promise.all([
|
|
417
|
+
import('../runtime/agent/orchestrator/session/store.mjs'),
|
|
418
|
+
import('../runtime/agent/orchestrator/session/manager/runtime-liveness.mjs'),
|
|
419
|
+
import('../runtime/agent/orchestrator/config.mjs'),
|
|
420
|
+
import('./workflow-agents-api/agent-editor.mjs'),
|
|
421
|
+
]);
|
|
422
|
+
// The editor's per-id definition probe: 'deleted' only when proven gone.
|
|
423
|
+
const { agentDefinitionPresence } = createAgentEditorApi({ cfgMod: { getPluginData } });
|
|
424
|
+
defaultDelivery = createAgentHomeDelivery({
|
|
425
|
+
loadSession,
|
|
426
|
+
saveSession: (session) => saveSessionAsync(session, { expectedGeneration: session.generation }),
|
|
427
|
+
turnInFlight: isSessionTurnInFlight,
|
|
428
|
+
queue: createRoutineResultQueue(join(getPluginData(), 'routine-results.json')),
|
|
429
|
+
agentPresence: (agentId) => agentDefinitionPresence([agentId])[agentId],
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
return defaultDelivery;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/** Queue one run's result for its owner's home thread. */
|
|
436
|
+
export async function deliverScheduleResult(schedule, result) {
|
|
437
|
+
return (await processDelivery()).enqueue(schedule, result);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/** Show a run's live-only row (running / delivery failed) in its owner's home. */
|
|
441
|
+
export async function announceRoutineRun(row, run) {
|
|
442
|
+
if (!liveSurface) return false;
|
|
443
|
+
return (await processDelivery()).announce(row, run);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/** Queue one held-request row for `agentId`'s home thread. */
|
|
447
|
+
export async function deliverHeldRequest(agentId, held) {
|
|
448
|
+
return (await processDelivery()).enqueueHeld(agentId, held);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/** Queue one report of a task session for `agentId`'s home thread. */
|
|
452
|
+
export async function deliverAgentTask(agentId, report) {
|
|
453
|
+
return (await processDelivery()).enqueueTask(agentId, report);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/** Daemon start: post the results an earlier process left queued. */
|
|
457
|
+
export async function recoverScheduleResults() {
|
|
458
|
+
return (await processDelivery()).recover();
|
|
459
|
+
}
|
|
@@ -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,92 @@
|
|
|
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
|
+
roomRowItem,
|
|
14
|
+
roomRowMessage,
|
|
15
|
+
} from '../runtime/agent/orchestrator/session/agent-room.mjs';
|
|
16
|
+
import {
|
|
17
|
+
attachmentsForIntake,
|
|
18
|
+
isAttachmentReference,
|
|
19
|
+
materializePromptSubmission,
|
|
20
|
+
preparePromptSubmissionForProvider,
|
|
21
|
+
} from '../runtime/attachments/store.mjs';
|
|
22
|
+
import { promptContentImageMeta } from '../tui/session/queue-helpers.mjs';
|
|
23
|
+
import { agentHomeLiveSurface } from './agent-home-delivery.mjs';
|
|
24
|
+
|
|
25
|
+
/** A user row with its `upload` taken in: the files as stored references
|
|
26
|
+
* (`parts`), their attachment records and the bubble's image chips. */
|
|
27
|
+
async function storedRow(row) {
|
|
28
|
+
const upload = row?.kind === 'user' && row.upload && typeof row.upload === 'object' ? row.upload : null;
|
|
29
|
+
if (!upload) return row;
|
|
30
|
+
const { upload: _upload, ...rest } = row;
|
|
31
|
+
// The words ride along so the composer's text tokens find their pastes.
|
|
32
|
+
const prompt = [{ type: 'text', text: String(row.text ?? '') }, ...(Array.isArray(upload.parts) ? upload.parts : [])];
|
|
33
|
+
const intake = await preparePromptSubmissionForProvider(
|
|
34
|
+
materializePromptSubmission(prompt, {
|
|
35
|
+
...(upload.pastedImages ? { pastedImages: upload.pastedImages } : {}),
|
|
36
|
+
...(upload.pastedTexts ? { pastedTexts: upload.pastedTexts } : {}),
|
|
37
|
+
}),
|
|
38
|
+
// Each member's own intake checks the PDF pages its provider takes.
|
|
39
|
+
{ nativePdf: false }
|
|
40
|
+
);
|
|
41
|
+
return {
|
|
42
|
+
...rest,
|
|
43
|
+
parts: intake.prompt.filter((part) => isAttachmentReference(part) && (part.type !== 'text' || part.textKind)),
|
|
44
|
+
attachments: attachmentsForIntake(intake.prompt, intake.options),
|
|
45
|
+
images: promptContentImageMeta(intake.prompt, intake.options.pastedImages) ?? [],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function createAgentRoomDelivery({ loadSession, saveSession, live = agentHomeLiveSurface, log = () => {} }) {
|
|
50
|
+
/** Append `rows` to the log of `sessionId`; resolves `{ added, rows }`: how
|
|
51
|
+
* many were new, and every row as stored (files as references, no bytes). */
|
|
52
|
+
async function append(sessionId, rows) {
|
|
53
|
+
if (!roomIdOfSession(sessionId)) throw new TypeError('not a group-room session');
|
|
54
|
+
const kept = (await Promise.all((Array.isArray(rows) ? rows : []).map(storedRow))).map((row) =>
|
|
55
|
+
normalizeRoomRow(row)
|
|
56
|
+
);
|
|
57
|
+
const messages = kept.map((row) => roomRowMessage(row));
|
|
58
|
+
const session = loadSession(sessionId);
|
|
59
|
+
if (!session || session.closed === true) throw new Error(`session ${sessionId} is not available`);
|
|
60
|
+
if (!Array.isArray(session.messages)) session.messages = [];
|
|
61
|
+
const posted = new Set(session.messages.map((message) => message?.meta?.transcript?.messageId).filter(Boolean));
|
|
62
|
+
const fresh = messages.filter((message) => !posted.has(message.meta.transcript.messageId));
|
|
63
|
+
if (!fresh.length) return { added: 0, rows: kept };
|
|
64
|
+
session.messages.push(...fresh);
|
|
65
|
+
session.updatedAt = Date.now();
|
|
66
|
+
await saveSession(session);
|
|
67
|
+
for (const message of fresh) {
|
|
68
|
+
const { text, tone, room, messageId, attachments, images } = roomRowItem(message);
|
|
69
|
+
try {
|
|
70
|
+
live()?.notice?.(sessionId, { text, tone, room, messageId, attachments, images });
|
|
71
|
+
} catch (error) {
|
|
72
|
+
log(`live room row in ${sessionId} failed: ${error?.message || error}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return { added: fresh.length, rows: kept };
|
|
76
|
+
}
|
|
77
|
+
return { append };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let processDelivery = null;
|
|
81
|
+
/** Daemon entry: post `rows` into the room log `sessionId`. */
|
|
82
|
+
export async function appendRoomRows(sessionId, rows) {
|
|
83
|
+
if (!processDelivery) {
|
|
84
|
+
const { loadSession, saveSessionAsync } = await import('../runtime/agent/orchestrator/session/store.mjs');
|
|
85
|
+
processDelivery = createAgentRoomDelivery({
|
|
86
|
+
loadSession,
|
|
87
|
+
saveSession: (session) => saveSessionAsync(session, { expectedGeneration: session.generation }),
|
|
88
|
+
log: (line) => process.stderr.write(`mixdog rooms: ${line}\n`),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return processDelivery.append(sessionId, rows);
|
|
92
|
+
}
|