mixdog 1.3.2 → 1.3.4
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/package.json +8 -8
- package/scripts/verify-runtime-dependency-versions.mjs +158 -0
- package/src/agents/reviewer/AGENT.md +5 -7
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +4 -0
- package/src/defaults/skills/browser-use/SKILL.md +6 -3
- package/src/defaults/skills/local-provider/SKILL.md +33 -6
- package/src/rules/lead/LEAD.md +5 -8
- package/src/rules/shared/45-destructive.md +5 -1
- package/src/rules/shared/60-verification.md +6 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +38 -11
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +15 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +17 -4
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/registry.mjs +10 -0
- package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +13 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +5 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +10 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +40 -5
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +32 -11
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +13 -0
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +68 -12
- package/src/runtime/agent/orchestrator/session/compact/continuation.mjs +15 -6
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +31 -8
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +44 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +117 -19
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +147 -137
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +37 -74
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +0 -2
- package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +13 -7
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/empty-nudge.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +62 -13
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +22 -6
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +12 -20
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/call-prep.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/manager/agent-listing-notice.mjs +102 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +24 -15
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +32 -1
- package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +17 -1
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +36 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -9
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +7 -5
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +7 -3
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +10 -1
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +7 -4
- package/src/runtime/agent/orchestrator/session/runtime-user-context.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +5 -4
- package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +140 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +76 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +33 -1
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.d.mts +5 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +37 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +18 -7
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +127 -20
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +557 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +6 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +8 -2
- package/src/runtime/attachments/legacy-office.mjs +110 -0
- package/src/runtime/attachments/prompt-restore.mjs +70 -0
- package/src/runtime/attachments/store.mjs +60 -3
- package/src/runtime/effort-judge/judge-client.mjs +61 -14
- package/src/runtime/effort-judge/model-install.mjs +34 -2
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +14 -2
- package/src/runtime/office/portable/portable-soffice.mjs +132 -30
- package/src/runtime/shared/agent-avatar-sprites.mjs +23 -8
- package/src/runtime/shared/agent-avatar.mjs +3 -2
- package/src/runtime/shared/agent-room-host.d.mts +31 -0
- package/src/runtime/shared/agent-room-host.mjs +58 -0
- package/src/runtime/shared/bot-hops.mjs +16 -11
- package/src/runtime/shared/llm/usage-rollup.mjs +4 -383
- package/src/runtime/shared/redact-directories.mjs +18 -0
- package/src/runtime/shared/remove-directory.mjs +28 -0
- package/src/runtime/shared/session-sidebar-host.d.mts +11 -2
- package/src/runtime/shared/session-sidebar-host.mjs +16 -3
- package/src/runtime/shared/tool-execution-contract.mjs +4 -0
- package/src/runtime/shared/tool-work-units.mjs +12 -9
- package/src/runtime/shared/turn-snapshot.mjs +233 -122
- package/src/runtime/shared/user-data-guard.mjs +51 -19
- package/src/runtime/shared/worktrees/activity-gate.mjs +209 -227
- package/src/runtime/shared/worktrees/contract.mjs +11 -3
- package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
- package/src/runtime/shared/worktrees/manager.mjs +419 -52
- package/src/runtime/shared/worktrees/registry.mjs +3 -1
- package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
- package/src/runtime/tidy/process.mjs +41 -8
- package/src/runtime/tidy/resolve.mjs +10 -7
- package/src/session-runtime/agent-home-delivery.mjs +73 -91
- package/src/session-runtime/agent-profile-changes.mjs +16 -0
- package/src/session-runtime/agent-room-delivery.mjs +2 -63
- package/src/session-runtime/agent-task-sessions.mjs +194 -64
- package/src/session-runtime/agent-tool-routing.mjs +49 -4
- package/src/session-runtime/boot/apis.mjs +3 -0
- package/src/session-runtime/boot/facade.mjs +5 -6
- package/src/session-runtime/boot/tools.mjs +23 -1
- package/src/session-runtime/held-requests.mjs +20 -25
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +5 -22
- package/src/session-runtime/model-recency.mjs +1 -0
- package/src/session-runtime/provider-models/row-hydration.mjs +8 -1
- package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
- package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
- package/src/session-runtime/runtime-review-api.mjs +4 -1
- package/src/session-runtime/services/agent-tool/close-flow.mjs +14 -2
- package/src/session-runtime/services/agent-tool/execute.mjs +6 -1
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +44 -20
- package/src/session-runtime/services/agent-tool/render.mjs +3 -1
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +3 -0
- package/src/session-runtime/services/agent-tool/tool-def.mjs +1 -1
- package/src/session-runtime/services/provider-admin.mjs +12 -2
- package/src/session-runtime/services/worktree-access.mjs +27 -0
- package/src/session-runtime/session-hooks.mjs +9 -17
- package/src/session-runtime/settings-builtin-tools-api.mjs +2 -0
- package/src/session-runtime/setup-tool/executor.mjs +4 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +3 -0
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +98 -14
- package/src/session-runtime/tool-policy/policy-stage.mjs +23 -5
- package/src/session-runtime/tool-policy-refresh.mjs +1 -0
- package/src/session-runtime/tool-surface.mjs +9 -5
- package/src/session-runtime/turn/session-ops.mjs +8 -0
- package/src/session-runtime/turn/turn-run.mjs +5 -1
- package/src/session-runtime/usage-stats-api.mjs +1 -1
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +10 -2
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +39 -17
- package/src/session-runtime/workspace/transition-steps.mjs +1 -18
- package/src/session-runtime/workspace/transitions.mjs +279 -67
- package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
- package/src/standalone/daemon-lag-profiler.mjs +3 -1
- package/src/standalone/daemon-session-control.mjs +140 -23
- package/src/standalone/daemon-telemetry.mjs +9 -0
- package/src/standalone/daemon.mjs +5 -0
- package/src/standalone/local-session-runtime.mjs +11 -2
- package/src/standalone/session-protocol.mjs +1 -0
- package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
- package/src/standalone/session-service/agent-route-follow.mjs +69 -0
- package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
- package/src/standalone/session-service.mjs +10 -0
- package/src/standalone/session-tool-output.mjs +1 -0
- package/src/standalone/staged-import.mjs +47 -0
- package/src/tui/App.jsx +2 -0
- package/src/tui/app/message-selector.mjs +15 -2
- package/src/tui/app/model-options.mjs +16 -2
- package/src/tui/app/model-picker.mjs +2 -1
- package/src/tui/app/route-pickers.mjs +3 -1
- package/src/tui/dist/index.mjs +403 -210
- package/src/tui/paste-attachments.mjs +0 -1
- package/src/tui/paste-text-policy.mjs +4 -5
- package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
- package/src/tui/session/notice-surface.mjs +3 -0
- package/src/tui/session/session-api/intake/abort.mjs +33 -17
- package/src/tui/session/session-api/intake/rewind.mjs +39 -4
- package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
- package/src/tui/session/tool-approval.mjs +11 -48
- package/src/vendor/statusline/src/gateway/claude-current.mjs +0 -58
- package/src/vendor/statusline/src/gateway/route-meta.mjs +1 -361
- package/src/vendor/statusline/src/gateway/session-routes.mjs +0 -55
- package/src/session-runtime/room-member-gate.mjs +0 -61
- package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +0 -285
|
@@ -18,8 +18,13 @@
|
|
|
18
18
|
*
|
|
19
19
|
* A group-room member (its member session `sess_rmem_<room>_<agent>`) opens
|
|
20
20
|
* and drives task sessions the same way; its reports carry `from` (the member
|
|
21
|
-
* session), so they land in
|
|
22
|
-
*
|
|
21
|
+
* session), so they land in that member session alone, never in its home or
|
|
22
|
+
* the room's log, and an ended turn gives the member a turn in its room to
|
|
23
|
+
* relay it (agent-home-delivery.mjs enqueueTask).
|
|
24
|
+
*
|
|
25
|
+
* A send to a group room's log goes to one member through the desktop room
|
|
26
|
+
* host (daemon-session-control.mjs): the member's turn in its member session
|
|
27
|
+
* is the reporting turn, taken by the room (`submit`), not by a prompt.
|
|
23
28
|
*/
|
|
24
29
|
import { randomUUID } from 'node:crypto';
|
|
25
30
|
import { realpathSync, statSync } from 'node:fs';
|
|
@@ -30,6 +35,9 @@ import { roomMemberOfSession } from '../runtime/agent/orchestrator/session/agent
|
|
|
30
35
|
import { wrapAgentMessage } from '../runtime/shared/agent-message-envelope.mjs';
|
|
31
36
|
import { sessionsWithPendingWork } from '../runtime/shared/pending-agent-work.mjs';
|
|
32
37
|
import { agentTaskIdOfExecution } from './services/agent-tool/restart-notify.mjs';
|
|
38
|
+
import { worktreeError, worktreeNameError } from '../runtime/shared/worktrees/contract.mjs';
|
|
39
|
+
import { samePath } from '../runtime/shared/worktrees/paths.mjs';
|
|
40
|
+
import { assertWorktreeMutationAllowed } from './services/worktree-access.mjs';
|
|
33
41
|
|
|
34
42
|
const TITLE_LIMIT = 60;
|
|
35
43
|
|
|
@@ -293,7 +301,7 @@ const WATCH_CAP_REASON =
|
|
|
293
301
|
const LAST_USED_ROUTE_MAX_PAGES = 20;
|
|
294
302
|
|
|
295
303
|
/** A report's `from` when its origin is a group-room member session: the
|
|
296
|
-
* report goes to that member
|
|
304
|
+
* report goes to that member session instead of the agent's home. */
|
|
297
305
|
const roomFrom = (originSessionId) => (roomMemberOfSession(originSessionId) ? { from: originSessionId } : {});
|
|
298
306
|
|
|
299
307
|
/** The route of `modelRef`: a provider/model[@effort…] ref (a superseded
|
|
@@ -514,20 +522,68 @@ async function deliverWithRetries({ deliver, retryDelaysMs, log }, agentId, sess
|
|
|
514
522
|
}
|
|
515
523
|
}
|
|
516
524
|
|
|
517
|
-
/**
|
|
518
|
-
*
|
|
519
|
-
|
|
525
|
+
/** Where create's `worktree` option starts the task session: `{ mode }` is
|
|
526
|
+
* 'default' (the user's New task setting), 'branch' (the Project folder on its
|
|
527
|
+
* current branch) or 'worktree' (its own new one, `name` optional). */
|
|
528
|
+
function taskLocation(option) {
|
|
529
|
+
if (option === undefined || option === null) return { mode: 'default' };
|
|
530
|
+
if (typeof option !== 'object' || Array.isArray(option) || !['branch', 'worktree'].includes(option.mode)) {
|
|
531
|
+
throw new Error('session: worktree.mode must be "branch" or "worktree"');
|
|
532
|
+
}
|
|
533
|
+
if (option.name === undefined) return { mode: option.mode };
|
|
534
|
+
if (option.mode !== 'worktree') throw new Error('session: worktree.name applies only to mode "worktree"');
|
|
535
|
+
const reason = worktreeNameError(option.name);
|
|
536
|
+
if (reason) throw new Error(`session: worktree.name is invalid: ${reason}`);
|
|
537
|
+
return { mode: 'worktree', name: option.name };
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const megabytes = (bytes) => `${Math.round(Number(bytes) / (1024 * 1024))} MB`;
|
|
541
|
+
|
|
542
|
+
/** A typed worktree failure as the model should read it: its code and, for a
|
|
543
|
+
* large repository, the size to put to the user. */
|
|
544
|
+
function taskWorktreeFailure(error) {
|
|
545
|
+
const details = error?.details;
|
|
546
|
+
const size =
|
|
547
|
+
error?.code === 'WORKTREE_LARGE_REPO_CONFIRM' && Number.isFinite(details?.estimateBytes)
|
|
548
|
+
? ` The repository is about ${megabytes(details.estimateBytes)} (warning threshold ${megabytes(details.thresholdBytes)}). Ask the user whether to create the worktree anyway; only if they confirm, call create again with confirm_large: true.`
|
|
549
|
+
: '';
|
|
550
|
+
return Object.assign(new Error(`session: ${error.code}: ${error.message}${size}`), {
|
|
551
|
+
code: error.code,
|
|
552
|
+
...(details ? { details } : {}),
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/** Start task session `sessionId` in its own worktree and return its workspace
|
|
557
|
+
* value. A New task never carries the Project folder's changes. Unless
|
|
558
|
+
* `explicit`, a repo that cannot have one keeps the Project folder (null).
|
|
559
|
+
* The session must end up bound: the brief goes out only after it. */
|
|
560
|
+
async function requestTaskWorktree(sessions, sessionId, { title, requestId, name, confirmLarge, explicit }) {
|
|
520
561
|
try {
|
|
521
|
-
await sessions.configureSession(
|
|
562
|
+
const result = await sessions.configureSession(
|
|
522
563
|
{
|
|
523
564
|
sessionId,
|
|
524
565
|
action: 'requestWorktree',
|
|
525
|
-
args: [
|
|
566
|
+
args: [
|
|
567
|
+
{
|
|
568
|
+
provision: true,
|
|
569
|
+
carryChanges: 'leave',
|
|
570
|
+
prompt: title,
|
|
571
|
+
txId: requestId,
|
|
572
|
+
...(name ? { name } : {}),
|
|
573
|
+
...(confirmLarge ? { confirmLarge: true } : {}),
|
|
574
|
+
},
|
|
575
|
+
],
|
|
526
576
|
},
|
|
527
577
|
{ revision: 1 }
|
|
528
578
|
);
|
|
579
|
+
const value = result?.value;
|
|
580
|
+
if (value?.pending !== false || typeof value.workspace?.worktreeId !== 'string') {
|
|
581
|
+
throw worktreeError('WORKTREE_NOT_IMPLEMENTED', 'The session did not enter its worktree before the brief.');
|
|
582
|
+
}
|
|
583
|
+
return value;
|
|
529
584
|
} catch (error) {
|
|
530
|
-
if (error?.code
|
|
585
|
+
if (!explicit && error?.code === 'WORKTREE_UNSUPPORTED_REPO') return null;
|
|
586
|
+
throw typeof error?.code === 'string' && error.code.startsWith('WORKTREE_') ? taskWorktreeFailure(error) : error;
|
|
531
587
|
}
|
|
532
588
|
}
|
|
533
589
|
|
|
@@ -565,6 +621,9 @@ async function rearmTaskWatch(
|
|
|
565
621
|
watch
|
|
566
622
|
) {
|
|
567
623
|
const sessionId = watch.taskSessionId;
|
|
624
|
+
// The turn that answers: the watched submission's own, or the one it
|
|
625
|
+
// follows (a room member's turn under another submission id).
|
|
626
|
+
const turnId = watch.follows || watch.runId;
|
|
568
627
|
const finish = async (status, text = '') =>
|
|
569
628
|
deliver(watch.agentId, {
|
|
570
629
|
runId: `${watch.runId}_end`,
|
|
@@ -578,7 +637,7 @@ async function rearmTaskWatch(
|
|
|
578
637
|
watchId: watch.runId,
|
|
579
638
|
...(watch.textOnly || text === GONE_TEXT || text === LOST_TEXT
|
|
580
639
|
? {}
|
|
581
|
-
: await artifactFields(sessionId,
|
|
640
|
+
: await artifactFields(sessionId, turnId)),
|
|
582
641
|
});
|
|
583
642
|
if (!(await loadSession(sessionId))) return finish('cancelled', GONE_TEXT);
|
|
584
643
|
let runtime;
|
|
@@ -607,20 +666,20 @@ async function rearmTaskWatch(
|
|
|
607
666
|
try {
|
|
608
667
|
do {
|
|
609
668
|
again = false;
|
|
610
|
-
const outcome = submissionOutcome((await loadSession(sessionId))?.messages,
|
|
669
|
+
const outcome = submissionOutcome((await loadSession(sessionId))?.messages, turnId);
|
|
611
670
|
const state = runtime?.getState?.() || {};
|
|
612
671
|
let end = outcome && outcome !== 'open' ? outcome : null;
|
|
613
672
|
if (!end && !state.busy) {
|
|
614
|
-
const queued = (state.queued || []).some((entry) => entry?.id ===
|
|
673
|
+
const queued = (state.queued || []).some((entry) => entry?.id === turnId);
|
|
615
674
|
if (outcome === 'open' && !queued) end = { status: 'cancelled', text: '' };
|
|
616
|
-
else if (!outcome && !queued && !(await queuedSubmissionIds(sessionId)).includes(
|
|
675
|
+
else if (!outcome && !queued && !(await queuedSubmissionIds(sessionId)).includes(turnId)) {
|
|
617
676
|
end = { status: 'cancelled', text: LOST_TEXT };
|
|
618
677
|
}
|
|
619
678
|
}
|
|
620
679
|
if (end?.status === 'completed') {
|
|
621
680
|
follow = followToFinal({
|
|
622
681
|
sessionId,
|
|
623
|
-
runId:
|
|
682
|
+
runId: turnId,
|
|
624
683
|
runtime,
|
|
625
684
|
wakeOwed: true,
|
|
626
685
|
progress: (text) =>
|
|
@@ -664,6 +723,8 @@ export function createAgentTaskSessions({
|
|
|
664
723
|
add: async (watch) => (await import('./agent-home-delivery.mjs')).watchAgentTask(watch),
|
|
665
724
|
list: async () => (await import('./agent-home-delivery.mjs')).agentTaskWatches(),
|
|
666
725
|
remove: async (runId) => (await import('./agent-home-delivery.mjs')).unwatchAgentTask(runId),
|
|
726
|
+
follow: async (runId, submissionId) =>
|
|
727
|
+
(await import('./agent-home-delivery.mjs')).followAgentTask(runId, submissionId),
|
|
667
728
|
},
|
|
668
729
|
// Submissions still queued (durably) for a session after a restart.
|
|
669
730
|
queuedSubmissionIds = async (sessionId) =>
|
|
@@ -736,8 +797,11 @@ export function createAgentTaskSessions({
|
|
|
736
797
|
);
|
|
737
798
|
|
|
738
799
|
/** Submit `prompt` as the origin agent's turn; its end (and a wait for the
|
|
739
|
-
* user in it) reports to the agent's home. Resolves on intake.
|
|
740
|
-
|
|
800
|
+
* user in it) reports to the agent's home. Resolves on intake. `submit`
|
|
801
|
+
* takes the turn in another way than a prompt to `sessionId` (a message
|
|
802
|
+
* into a group room, whose member answers in `sessionId`): given `{ id,
|
|
803
|
+
* onSettled }`, it resolves `{ accepted }`. */
|
|
804
|
+
async function agentTurn(sessionId, prompt, origin, id = `atask_${randomUUID()}`, beforeReport = null, submit = null) {
|
|
741
805
|
const sessions = service();
|
|
742
806
|
const target = origin.target === 'session' ? { target: 'session' } : {};
|
|
743
807
|
const { notify, noReport } = await watchTurn({ watches, owner: processToken, log }, sessionId, origin, id);
|
|
@@ -789,48 +853,49 @@ export function createAgentTaskSessions({
|
|
|
789
853
|
const sent = origin.envelopeName
|
|
790
854
|
? wrapAgentMessage(prompt, { name: origin.envelopeName, agentId: origin.agentId })
|
|
791
855
|
: prompt;
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
...(origin.envelopeName ? { fromAgent: origin.agentId } : {}),
|
|
804
|
-
...(origin.hop > 0 ? { hop: origin.hop } : {}),
|
|
805
|
-
},
|
|
806
|
-
...(notify
|
|
807
|
-
? {
|
|
808
|
-
onSettled: (detail) => {
|
|
809
|
-
const status = turnStatus(detail);
|
|
810
|
-
const text = detail?.result?.content || detail?.error || '';
|
|
811
|
-
// A failed or cancelled turn reports at once; a completed one
|
|
812
|
-
// reports the task's final answer (followToFinal).
|
|
813
|
-
if (status !== 'completed') {
|
|
814
|
-
unwatch();
|
|
815
|
-
void report(status, text);
|
|
816
|
-
return;
|
|
817
|
-
}
|
|
818
|
-
follow = followToFinal({
|
|
819
|
-
sessionId,
|
|
820
|
-
runId: id,
|
|
821
|
-
runtime,
|
|
822
|
-
immediate: { status, text },
|
|
823
|
-
finish: async (endStatus, endText) => {
|
|
824
|
-
unwatch();
|
|
825
|
-
await report(endStatus, endText);
|
|
826
|
-
},
|
|
827
|
-
progress: (progressText) => report('working', progressText),
|
|
828
|
-
});
|
|
829
|
-
follow.check();
|
|
830
|
-
},
|
|
831
|
-
}
|
|
832
|
-
: {}),
|
|
856
|
+
const take = submit ?? ((options) => sessions.submitSession({ sessionId, prompt: sent, options }));
|
|
857
|
+
result = await take({
|
|
858
|
+
id,
|
|
859
|
+
mode: 'prompt',
|
|
860
|
+
// `fromAgent`: the sending bot, recorded when it writes into another
|
|
861
|
+
// bot's home chat (otherwise the row would read as the user's);
|
|
862
|
+
// `hop`: its place in a bot-to-bot chain (bot-hops.mjs).
|
|
863
|
+
transcriptMeta: {
|
|
864
|
+
sender: 'lead',
|
|
865
|
+
...(origin.envelopeName ? { fromAgent: origin.agentId } : {}),
|
|
866
|
+
...(origin.hop > 0 ? { hop: origin.hop } : {}),
|
|
833
867
|
},
|
|
868
|
+
...(notify
|
|
869
|
+
? {
|
|
870
|
+
// A room answers this send in a turn under another submission
|
|
871
|
+
// id (one turn for several sends, or a fresh turn for one cut
|
|
872
|
+
// short): the watch follows that turn, recorded before it runs.
|
|
873
|
+
follow: (submissionId) => watches.follow?.(id, submissionId),
|
|
874
|
+
onSettled: (detail) => {
|
|
875
|
+
const status = turnStatus(detail);
|
|
876
|
+
const text = detail?.result?.content || detail?.error || '';
|
|
877
|
+
// A failed or cancelled turn reports at once; a completed one
|
|
878
|
+
// reports the task's final answer (followToFinal).
|
|
879
|
+
if (status !== 'completed') {
|
|
880
|
+
unwatch();
|
|
881
|
+
void report(status, text);
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
follow = followToFinal({
|
|
885
|
+
sessionId,
|
|
886
|
+
runId: id,
|
|
887
|
+
runtime,
|
|
888
|
+
immediate: { status, text },
|
|
889
|
+
finish: async (endStatus, endText) => {
|
|
890
|
+
unwatch();
|
|
891
|
+
await report(endStatus, endText);
|
|
892
|
+
},
|
|
893
|
+
progress: (progressText) => report('working', progressText),
|
|
894
|
+
});
|
|
895
|
+
follow.check();
|
|
896
|
+
},
|
|
897
|
+
}
|
|
898
|
+
: {}),
|
|
834
899
|
});
|
|
835
900
|
} catch (error) {
|
|
836
901
|
unwatch();
|
|
@@ -882,15 +947,31 @@ export function createAgentTaskSessions({
|
|
|
882
947
|
return pending.length;
|
|
883
948
|
}
|
|
884
949
|
|
|
885
|
-
/** Create the task session for `brief` and start it; returns at once.
|
|
950
|
+
/** Create the task session for `brief` and start it; returns at once.
|
|
951
|
+
* `callerScope` is the caller's trusted worktree scope (worktree-access.mjs
|
|
952
|
+
* worktreeCallerScope): provisioning a worktree is a mutation only an
|
|
953
|
+
* attended conversation may make, so an explicit worktree from any other
|
|
954
|
+
* caller is refused and the New task default keeps it in the Project folder. */
|
|
886
955
|
async function create(
|
|
887
956
|
args = {},
|
|
888
|
-
{ callerSessionId, callerAgentId, callerCwd, callerHop = 0, attachmentParts = [] } = {}
|
|
957
|
+
{ callerSessionId, callerAgentId, callerCwd, callerHop = 0, callerScope, attachmentParts = [] } = {}
|
|
889
958
|
) {
|
|
890
959
|
if (!callerAgentId) throw new Error('session: create is only for an agent conversation');
|
|
891
960
|
const brief = typeof args.brief === 'string' ? args.brief.trim() : '';
|
|
892
961
|
if (!brief) throw new Error('session: brief is required for action=create');
|
|
893
|
-
const
|
|
962
|
+
const location = taskLocation(args.worktree);
|
|
963
|
+
if (location.mode === 'worktree') assertWorktreeMutationAllowed(callerScope, 'session: create with a worktree');
|
|
964
|
+
const projects = await listProjects();
|
|
965
|
+
const where = resolveTaskProject(args.project, { projects, cwd: callerCwd || process.cwd() });
|
|
966
|
+
// An explicit worktree is never silently dropped: it needs a registered Project.
|
|
967
|
+
const registered = where.project && projects.some((entry) => samePath(entry.path, where.project.path));
|
|
968
|
+
if (location.mode === 'worktree' && !registered) {
|
|
969
|
+
throw taskWorktreeFailure(
|
|
970
|
+
worktreeError('WORKTREE_PROJECT_UNKNOWN', 'A worktree needs a registered Project; run list-projects.', {
|
|
971
|
+
projectPath: where.project?.path ?? null,
|
|
972
|
+
})
|
|
973
|
+
);
|
|
974
|
+
}
|
|
894
975
|
const requestId = `atask_${randomUUID()}`;
|
|
895
976
|
const title = (clean(args.title) || clean(brief.split('\n')[0])).slice(0, TITLE_LIMIT);
|
|
896
977
|
const taskOrigin = {
|
|
@@ -908,6 +989,16 @@ export function createAgentTaskSessions({
|
|
|
908
989
|
: (await leadRoute()) || (await lastUsedRoute(service()));
|
|
909
990
|
if (!route) throw new Error('session: no model to start the task session on. Choose a Lead model first.');
|
|
910
991
|
const sessions = service();
|
|
992
|
+
// Where it starts: the `worktree` option, else the user's New task default
|
|
993
|
+
// for a caller that may provision one. Decided before the session exists,
|
|
994
|
+
// so a failure here leaves nothing behind.
|
|
995
|
+
const explicit = location.mode === 'worktree';
|
|
996
|
+
const inWorktree =
|
|
997
|
+
explicit ||
|
|
998
|
+
(location.mode === 'default' &&
|
|
999
|
+
where.project &&
|
|
1000
|
+
callerScope?.attended === true &&
|
|
1001
|
+
(await newTaskDefault()) === 'worktree');
|
|
911
1002
|
const created = await sessions.createSession({
|
|
912
1003
|
...route,
|
|
913
1004
|
cwd: where.cwd,
|
|
@@ -916,9 +1007,43 @@ export function createAgentTaskSessions({
|
|
|
916
1007
|
});
|
|
917
1008
|
const sessionId = String(created?.sessionId || '');
|
|
918
1009
|
if (!sessionId) throw new Error('session: task session creation returned no session id');
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1010
|
+
if (inWorktree) {
|
|
1011
|
+
try {
|
|
1012
|
+
await requestTaskWorktree(sessions, sessionId, {
|
|
1013
|
+
title,
|
|
1014
|
+
requestId,
|
|
1015
|
+
name: location.name,
|
|
1016
|
+
confirmLarge: args.confirm_large === true,
|
|
1017
|
+
explicit,
|
|
1018
|
+
});
|
|
1019
|
+
} catch (error) {
|
|
1020
|
+
// Nothing was sent: drop the empty session so a retry starts clean.
|
|
1021
|
+
let dropFailure = null;
|
|
1022
|
+
try {
|
|
1023
|
+
const dropped = await sessions.configureSession(
|
|
1024
|
+
{ sessionId, action: 'deleteSession', args: [sessionId] },
|
|
1025
|
+
{ revision: 1 }
|
|
1026
|
+
);
|
|
1027
|
+
// A resolved `false` is a refusal, not a deletion.
|
|
1028
|
+
if (dropped?.value === false) dropFailure = new Error('the session service refused the deletion');
|
|
1029
|
+
} catch (deleteError) {
|
|
1030
|
+
dropFailure = deleteError;
|
|
1031
|
+
}
|
|
1032
|
+
if (dropFailure) log(`dropping ${sessionId} failed: ${dropFailure?.message || dropFailure}`);
|
|
1033
|
+
if (!dropFailure) throw error;
|
|
1034
|
+
// The model must know the empty session is still there to delete.
|
|
1035
|
+
throw Object.assign(
|
|
1036
|
+
new Error(
|
|
1037
|
+
`${error?.message || error} The empty task session ${sessionId} could not be deleted either (${dropFailure?.message || dropFailure}); delete it with session action=delete session_id=${sessionId}.`
|
|
1038
|
+
),
|
|
1039
|
+
{
|
|
1040
|
+
...(typeof error?.code === 'string' ? { code: error.code } : {}),
|
|
1041
|
+
...(error?.details ? { details: error.details } : {}),
|
|
1042
|
+
orphanSessionId: sessionId,
|
|
1043
|
+
cause: error,
|
|
1044
|
+
}
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
922
1047
|
}
|
|
923
1048
|
// "Started" lands in the home before any report of the brief's turn.
|
|
924
1049
|
let started = () => {};
|
|
@@ -958,5 +1083,10 @@ export function createAgentTaskSessions({
|
|
|
958
1083
|
const reportOrigin = (sessionId, agentId, callerSessionId) =>
|
|
959
1084
|
sendReportOrigin({ loadSession, agentName }, sessionId, agentId, callerSessionId);
|
|
960
1085
|
|
|
961
|
-
return {
|
|
1086
|
+
return {
|
|
1087
|
+
create,
|
|
1088
|
+
reportOrigin,
|
|
1089
|
+
recover,
|
|
1090
|
+
send: (sessionId, prompt, origin, submit = null) => agentTurn(sessionId, prompt, origin, undefined, null, submit),
|
|
1091
|
+
};
|
|
962
1092
|
}
|
|
@@ -4,12 +4,57 @@
|
|
|
4
4
|
* status for the facade.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
import { clean } from '../runtime/shared/clean.mjs';
|
|
8
|
+
|
|
9
|
+
const lower = (value) => clean(value).toLowerCase();
|
|
10
|
+
|
|
11
|
+
// A pinned model named by its catalog display name ("GPT-6.1-Sol") is not a
|
|
12
|
+
// wire id. Canonicalize it only on an unambiguous EXACT match: the id itself,
|
|
13
|
+
// or a display name / label, case-insensitively, within the requested provider
|
|
14
|
+
// (or across providers when none is given and exactly one row matches).
|
|
15
|
+
// Anything else — no match, several matches, an empty or partial catalog —
|
|
16
|
+
// returns the args unchanged: the catalog never refuses a model and never
|
|
17
|
+
// substitutes a near one.
|
|
18
|
+
export function canonicalizePinnedModel(args, rows) {
|
|
19
|
+
const model = clean(args?.model);
|
|
20
|
+
if (!model || !Array.isArray(rows)) return args;
|
|
21
|
+
const provider = lower(args.provider);
|
|
22
|
+
const pool = rows.filter((row) => row && (!provider || lower(row.provider) === provider));
|
|
23
|
+
const wanted = model.toLowerCase();
|
|
24
|
+
const exactId = pool.filter((row) => row.id === model);
|
|
25
|
+
// A partial catalog may lack the real id a display name collides with, so
|
|
26
|
+
// only an exact id can pass there; aliases wait for a complete catalog.
|
|
27
|
+
const partial = pool.some((row) => row.catalogPartial === true);
|
|
28
|
+
const hits = exactId.length
|
|
29
|
+
? exactId
|
|
30
|
+
: partial
|
|
31
|
+
? []
|
|
32
|
+
: pool.filter((row) => lower(row.id) === wanted || lower(row.display) === wanted || lower(row.label) === wanted);
|
|
33
|
+
const distinct = [...new Map(hits.map((row) => [`${row.provider}\n${row.id}`, row])).values()];
|
|
34
|
+
if (distinct.length !== 1) return args;
|
|
35
|
+
const [hit] = distinct;
|
|
36
|
+
if (hit.id === model && hit.provider === args.provider) return args;
|
|
37
|
+
return { ...args, provider: hit.provider, model: hit.id };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// `listProviderModels` is this runtime's provider catalog. The agent control
|
|
41
|
+
// behind `executeAgentControl` (the daemon's canonical agent) has none, so a
|
|
42
|
+
// display-name model is canonicalized here before it is forwarded. Only the
|
|
43
|
+
// cached/quick catalog is read, so a spawn never waits on provider listings.
|
|
44
|
+
export function createRoutedAgentTool({ rt, agentTool, executeAgentControl = null, listProviderModels = null }) {
|
|
8
45
|
const routedAgentTool = {
|
|
9
46
|
...agentTool,
|
|
10
|
-
execute(args, context = {}) {
|
|
11
|
-
if (typeof executeAgentControl
|
|
12
|
-
|
|
47
|
+
async execute(args, context = {}) {
|
|
48
|
+
if (typeof executeAgentControl !== 'function') return agentTool.execute(args, context);
|
|
49
|
+
let routedArgs = args;
|
|
50
|
+
if (typeof listProviderModels === 'function' && clean(args?.model) && (clean(args.type) || 'spawn') === 'spawn') {
|
|
51
|
+
try {
|
|
52
|
+
routedArgs = canonicalizePinnedModel(args, await listProviderModels({ quick: true }));
|
|
53
|
+
} catch {
|
|
54
|
+
routedArgs = args;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return executeAgentControl(routedArgs, context);
|
|
13
58
|
},
|
|
14
59
|
closeAll(reason, scope = {}) {
|
|
15
60
|
if (typeof executeAgentControl !== 'function') {
|
|
@@ -167,6 +167,7 @@ function wireToolPolicyRefresh(boot) {
|
|
|
167
167
|
applyPendingSessionSwitches,
|
|
168
168
|
refreshAgentProfile,
|
|
169
169
|
applyPendingAgentProfile,
|
|
170
|
+
currentAgents,
|
|
170
171
|
} = createToolPolicyRefresh({
|
|
171
172
|
getSession: () => rt.session,
|
|
172
173
|
getRoute: () => rt.route,
|
|
@@ -193,6 +194,7 @@ function wireToolPolicyRefresh(boot) {
|
|
|
193
194
|
boot.applyPendingSessionSwitches = applyPendingSessionSwitches;
|
|
194
195
|
boot.refreshAgentProfile = refreshAgentProfile;
|
|
195
196
|
boot.applyPendingAgentProfile = applyPendingAgentProfile;
|
|
197
|
+
boot.currentAgents = currentAgents;
|
|
196
198
|
// A saved bot reaches this runtime's thread when its prompt embeds it (it
|
|
197
199
|
// runs as that bot, or lists it); the rebuilt prompt is saved with the session.
|
|
198
200
|
const profileChanges = subscribeAgentProfileChanges(async () => {
|
|
@@ -660,6 +662,7 @@ function sessionTurnApiFor(boot) {
|
|
|
660
662
|
return createSessionTurnApi({
|
|
661
663
|
applyPendingRoute: () => boot.modelRouteApi.applyPendingRoute(),
|
|
662
664
|
applyPendingAgentProfile: boot.applyPendingAgentProfile,
|
|
665
|
+
currentAgents: boot.currentAgents,
|
|
663
666
|
applyPendingSessionSwitches: boot.applyPendingSessionSwitches,
|
|
664
667
|
prepareTurnWorkspace: boot.workspaceApi.prepareTurnWorkspace,
|
|
665
668
|
admitTurnWorkspace: boot.workspaceApi.admitTurnWorkspace,
|
|
@@ -7,6 +7,7 @@ import { createRuntimeFacade } from '../runtime-facade.mjs';
|
|
|
7
7
|
import { bootProfile } from '../boot-profile.mjs';
|
|
8
8
|
import { assertAdmissionOpen, bindSessionAddress } from '../session-address.mjs';
|
|
9
9
|
import { dataDirOf, workflowHelpers } from './shared.mjs';
|
|
10
|
+
import { publicWorkspaceMethods } from '../workspace/transitions.mjs';
|
|
10
11
|
|
|
11
12
|
export function buildFacade(boot) {
|
|
12
13
|
const { rt, setupTool } = boot;
|
|
@@ -41,16 +42,14 @@ export function buildFacade(boot) {
|
|
|
41
42
|
getLiveWorkspace: boot.workspaceApi.getLiveWorkspace,
|
|
42
43
|
getBranchMenu: boot.workspaceApi.getBranchMenu,
|
|
43
44
|
getProjectBranchMenu: boot.workspaceApi.getProjectBranchMenu,
|
|
44
|
-
selectBranch: boot.workspaceApi.selectBranch,
|
|
45
45
|
selectProjectBranch: boot.workspaceApi.selectProjectBranch,
|
|
46
46
|
mergeProjectBranch: boot.workspaceApi.mergeProjectBranch,
|
|
47
47
|
inspectProjectBranch: boot.workspaceApi.inspectProjectBranch,
|
|
48
48
|
manageProjectBranch: boot.workspaceApi.manageProjectBranch,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
pruneWorktrees: boot.workspaceApi.pruneWorktrees,
|
|
49
|
+
// selectBranch, requestWorktree, mergeWorkspace, removeWorktree,
|
|
50
|
+
// listWorktrees, pruneWorktrees: the public arity only, so no external
|
|
51
|
+
// caller (desktop IPC, the daemon's configure lane) reaches `ownTurn`.
|
|
52
|
+
...publicWorkspaceMethods(boot.workspaceApi),
|
|
54
53
|
onWorktreeSetup: boot.workspaceApi.onWorktreeSetup,
|
|
55
54
|
claimSetupRequest: setupTool.claimSetupRequest,
|
|
56
55
|
isSetupRequestActive: setupTool.isSetupRequestActive,
|
|
@@ -34,6 +34,8 @@ import {
|
|
|
34
34
|
createContextStatus,
|
|
35
35
|
} from '../../runtime/agent/orchestrator/runtime-core/context-status.mjs';
|
|
36
36
|
import { createInternalToolExecutor } from '../internal-tool-executor.mjs';
|
|
37
|
+
import { registerWorktreeToolHost } from '../../runtime/shared/worktrees/tool-host.mjs';
|
|
38
|
+
import { createWorktreeToolHost } from '../workspace/worktree-tool.mjs';
|
|
37
39
|
import { STANDALONE_ROOT, standaloneDataDir } from '../runtime-paths.mjs';
|
|
38
40
|
import { dataDirOf, workflowHelpers } from './shared.mjs';
|
|
39
41
|
|
|
@@ -96,6 +98,8 @@ function wireAgent(boot) {
|
|
|
96
98
|
rt,
|
|
97
99
|
agentTool,
|
|
98
100
|
executeAgentControl: params.executeAgentControl,
|
|
101
|
+
// Lazy, as above.
|
|
102
|
+
listProviderModels: (options) => boot.collectProviderModels(options),
|
|
99
103
|
});
|
|
100
104
|
bootProfile('agent:ready', { ms: (performance.now() - agentToolStartedAt).toFixed(1) });
|
|
101
105
|
Object.assign(boot, { agentTool, routedAgentTool, agentStatusState });
|
|
@@ -261,5 +265,23 @@ function wireInternalTools(boot) {
|
|
|
261
265
|
executeSessionControl: boot.params.executeSessionControl,
|
|
262
266
|
}),
|
|
263
267
|
});
|
|
264
|
-
|
|
268
|
+
// The git tool's managed-worktree operations reach this Lead's workspace
|
|
269
|
+
// authority (late-bound like the internal tools) through this host.
|
|
270
|
+
const disposeWorktreeToolHost = registerWorktreeToolHost({
|
|
271
|
+
sessionId: () => rt.session?.id || null,
|
|
272
|
+
run: createWorktreeToolHost({
|
|
273
|
+
getSession: () => rt.session,
|
|
274
|
+
getWorkspaceApi: () => boot.workspaceApi || null,
|
|
275
|
+
executeSessionControl: boot.params.executeSessionControl || null,
|
|
276
|
+
}).run,
|
|
277
|
+
});
|
|
278
|
+
Object.assign(boot, {
|
|
279
|
+
setupTool,
|
|
280
|
+
disposeInternalTools: () => {
|
|
281
|
+
disposeWorktreeToolHost();
|
|
282
|
+
// Its turn snapshot cleanups stop with the runtime (maintenance finishes them).
|
|
283
|
+
boot.workspaceApi?.dispose();
|
|
284
|
+
disposeInternalTools();
|
|
285
|
+
},
|
|
286
|
+
});
|
|
265
287
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* the user's grant; human-gate/run-policy.mjs), so only group
|
|
4
|
-
*
|
|
2
|
+
* Held requests. A schedule or webhook run never asks and never holds (its
|
|
3
|
+
* saved routine is the user's grant; human-gate/run-policy.mjs), so only group
|
|
4
|
+
* room members use held requests: with no
|
|
5
|
+
* approval UI attached, an approval-needing call is HELD instead of shown as
|
|
5
6
|
* a card: the run pauses on it, a structured held-request row lands in the
|
|
6
7
|
* owning agent's home thread (agent-home-delivery.mjs queue), a phone push
|
|
7
8
|
* says only "<agent>가 승인을 기다립니다" (no tool, arguments or text), and the
|
|
@@ -24,8 +25,8 @@ import {
|
|
|
24
25
|
setUnattendedApprovalPort,
|
|
25
26
|
} from '../runtime/agent/orchestrator/session/human-gate/gate.mjs';
|
|
26
27
|
import { normalizeHumanResponse } from '../runtime/agent/orchestrator/session/human-gate/requests.mjs';
|
|
28
|
+
import { runStampOf } from '../runtime/agent/orchestrator/session/human-gate/delegation.mjs';
|
|
27
29
|
import { normalizeHeldRequest } from '../runtime/agent/orchestrator/session/human-gate/held-message.mjs';
|
|
28
|
-
import { delegationOf } from '../runtime/agent/orchestrator/session/human-gate/delegation.mjs';
|
|
29
30
|
import {
|
|
30
31
|
notifyHeldRequest,
|
|
31
32
|
recordRunOwner,
|
|
@@ -49,28 +50,21 @@ export function markUnattendedRun(sessionId, { kind, agentId, name = '' }) {
|
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
/** The unattended run `sessionId`
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* long ago that run ended, across
|
|
57
|
-
*
|
|
53
|
+
/** The unattended run `sessionId` is, or null for an attended session: a
|
|
54
|
+
* marked schedule/webhook run, a room member's own session, or work a
|
|
55
|
+
* schedule/webhook run started (a sub-agent at any depth, a task session) by
|
|
56
|
+
* the `run` stamped on its own record when it was created
|
|
57
|
+
* (human-gate/delegation.mjs) — however long ago that run ended, across
|
|
58
|
+
* restarts. Work a bot's attended chat or a room member started is no run. */
|
|
58
59
|
export function unattendedRunOf(sessionId) {
|
|
59
60
|
const run = runs.get(sessionId);
|
|
60
61
|
if (run) return run;
|
|
61
62
|
const member = roomMemberOfSession(sessionId);
|
|
62
63
|
if (member) return { kind: 'room', agentId: member.agentId, name: member.roomId };
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
kind: delegation.run.kind === 'webhook' ? 'webhook' : 'schedule',
|
|
68
|
-
agentId: delegation.rootAgentId,
|
|
69
|
-
name: String(delegation.run.name || ''),
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
const root = delegation.via === 'room' ? roomMemberOfSession(delegation.rootSessionId) : null;
|
|
73
|
-
return root ? { kind: 'room', agentId: root.agentId, name: root.roomId } : null;
|
|
64
|
+
const stamped = runStampOf(sessionId);
|
|
65
|
+
return stamped && (stamped.kind === 'schedule' || stamped.kind === 'webhook') && stamped.agentId
|
|
66
|
+
? { kind: stamped.kind, agentId: String(stamped.agentId), name: String(stamped.name || '') }
|
|
67
|
+
: null;
|
|
74
68
|
}
|
|
75
69
|
|
|
76
70
|
/**
|
|
@@ -134,6 +128,8 @@ export function createHeldRequests({
|
|
|
134
128
|
/** Hold `request` (the approval hook's payload) until answered or the run is cancelled. */
|
|
135
129
|
function hold(request = {}, { signal } = {}) {
|
|
136
130
|
const run = runOf(request.sessionId) || { kind: 'schedule', agentId: MIXY_AGENT_ID, name: '' };
|
|
131
|
+
// The scopes the gate offered (gate.mjs): the bot's own grant, or once.
|
|
132
|
+
const remember = Array.isArray(request.remember) && request.remember.length ? request.remember : ['once'];
|
|
137
133
|
const id = `held_${randomUUID()}`;
|
|
138
134
|
const held = normalizeHeldRequest({
|
|
139
135
|
id,
|
|
@@ -152,13 +148,12 @@ export function createHeldRequests({
|
|
|
152
148
|
details: request.outbound?.details,
|
|
153
149
|
}
|
|
154
150
|
: {}),
|
|
155
|
-
|
|
156
|
-
remember: ['once'],
|
|
151
|
+
remember,
|
|
157
152
|
run: { kind: run.kind, name: run.name, sessionId: request.sessionId },
|
|
158
153
|
});
|
|
159
|
-
const shape = { kind: held.kind, outbound: request.outbound, remember
|
|
154
|
+
const shape = { kind: held.kind, outbound: request.outbound, remember };
|
|
160
155
|
const gate = {
|
|
161
|
-
remember
|
|
156
|
+
remember,
|
|
162
157
|
agentId: run.agentId,
|
|
163
158
|
kind: held.kind,
|
|
164
159
|
...(held.action ? { action: held.action } : {}),
|