mixdog 1.3.4 → 1.3.5
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/oh-my-pi-MIT.txt +23 -0
- package/NOTICE.md +15 -0
- package/README.md +11 -0
- package/package.json +2 -1
- package/scripts/embed-google-oauth-client.mjs +49 -0
- package/scripts/lib/test-spawn-ledger.mjs +46 -1
- package/scripts/test.mjs +5 -0
- package/src/defaults/shipped-history.json +8 -0
- package/src/defaults/skills/browser-use/SKILL.md +21 -9
- package/src/defaults/skills/setup/SKILL.md +7 -2
- package/src/defaults/skills/setup/references/actions.md +82 -27
- package/src/defaults/skills/setup/references/surfaces.md +67 -34
- package/src/defaults/skills/setup-guide/SKILL.md +3 -3
- package/src/lib/keychain-cjs.cjs +359 -29
- package/src/rules/agent-mode/AGENT_MODE.md +10 -2
- package/src/rules/agent-mode/MIXY.md +12 -0
- package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +193 -0
- package/src/runtime/agent/orchestrator/config.mjs +18 -0
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +2 -2
- package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
- package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +31 -2
- package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +16 -3
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +42 -7
- package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +21 -2
- package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +23 -0
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +11 -1
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +68 -23
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +22 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +18 -5
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -3
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +52 -8
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +10 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +22 -6
- package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +15 -4
- package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +17 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +19 -2
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +10 -4
- package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +10 -217
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +409 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-families.mjs +187 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-identity.mjs +342 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-models.mjs +541 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-protobuf.mjs +305 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-request.mjs +558 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-schema.mjs +880 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-session.mjs +190 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-tool-args.mjs +351 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +560 -0
- package/src/runtime/agent/orchestrator/providers/devin.mjs +491 -0
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/gemini-output-limit.mjs +46 -0
- package/src/runtime/agent/orchestrator/providers/gemini-reasoning-relay.mjs +30 -0
- package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +7 -11
- package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +61 -22
- package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -4
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +25 -2
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +96 -5
- package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +23 -5
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +17 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +39 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/provider-availability.mjs +19 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/wire-protobuf-codec.mjs +368 -0
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/reaction-tool-defs.mjs +133 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +23 -5
- package/src/runtime/agent/orchestrator/runtime-core/tool-profile.mjs +35 -10
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +57 -10
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +9 -6
- package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +18 -0
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +0 -3
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +10 -4
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +15 -3
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +36 -4
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +15 -3
- package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +8 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +50 -2
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +60 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/message-ids.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/message-reactions.mjs +89 -0
- package/src/runtime/agent/orchestrator/session/message-reactions.test-support.mjs +75 -0
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +0 -2
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +20 -2
- package/src/runtime/agent/orchestrator/session/store-agent-worker-header-cache.mjs +85 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +125 -12
- package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +7 -1
- package/src/runtime/channels/lib/schedule-errors.mjs +51 -0
- package/src/runtime/channels/lib/scheduler.mjs +109 -14
- package/src/runtime/channels/lib/schedules-db.mjs +49 -0
- package/src/runtime/channels/lib/tool-dispatch.mjs +13 -0
- package/src/runtime/channels/lib/worker-main.mjs +9 -0
- package/src/runtime/office/com/com-adapter.mjs +17 -1
- package/src/runtime/office/com/office-com-host.ps1 +51 -1
- package/src/runtime/office/pdf/cfb-fixture.mjs +328 -0
- package/src/runtime/office/pdf/cfb-protection.mjs +311 -0
- package/src/runtime/office/pdf/document-preview.mjs +326 -8
- package/src/runtime/office/pdf/ooxml-crypto.mjs +469 -0
- package/src/runtime/office/pdf/ooxml-key-worker.mjs +14 -0
- package/src/runtime/office/portable/portable-soffice.mjs +45 -5
- package/src/runtime/shared/bot-hops.mjs +62 -2
- package/src/runtime/shared/config.mjs +49 -22
- package/src/runtime/shared/connector-catalog.d.mts +4 -2
- package/src/runtime/shared/connector-catalog.mjs +16 -8
- package/src/runtime/shared/developer-options.mjs +32 -1
- package/src/runtime/shared/google-oauth-client.generated.json +4 -0
- package/src/runtime/shared/google-oauth-client.mjs +34 -9
- package/src/runtime/shared/llm/anthropic-betas.mjs +7 -0
- package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +12 -0
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +1 -0
- package/src/runtime/shared/llm/well-formed.mjs +66 -0
- package/src/runtime/shared/provider-accounts.mjs +224 -58
- package/src/runtime/shared/provider-api-key.mjs +19 -8
- package/src/runtime/shared/routine-trigger.mjs +42 -0
- package/src/runtime/shared/tool-execution-contract.mjs +2 -0
- package/src/runtime/shared/transcript-metadata.mjs +38 -0
- package/src/runtime/shared/user-data-guard.mjs +30 -2
- package/src/runtime/shared/worktrees/activity-gate.mjs +12 -5
- package/src/session-runtime/agent-home-delivery.mjs +57 -13
- package/src/session-runtime/agent-room-delivery.mjs +28 -1
- package/src/session-runtime/agent-task-sessions.mjs +292 -79
- package/src/session-runtime/boot/begin.mjs +3 -2
- package/src/session-runtime/boot/tools.mjs +2 -0
- package/src/session-runtime/bot-chain.mjs +32 -0
- package/src/session-runtime/bot-status.mjs +7 -4
- package/src/session-runtime/channel-config-api.mjs +63 -17
- package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +45 -0
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +72 -5
- package/src/session-runtime/internal-tool-executor.mjs +4 -0
- package/src/session-runtime/mcp-glue/server-catalog.mjs +7 -1
- package/src/session-runtime/mixy-heartbeat.mjs +807 -0
- package/src/session-runtime/model-route/route-tuning.mjs +10 -6
- package/src/session-runtime/owned-automation-run.mjs +33 -12
- package/src/session-runtime/provider-auth/login.mjs +7 -0
- package/src/session-runtime/provider-models/row-hydration.mjs +13 -2
- package/src/session-runtime/routine-home-turn.mjs +33 -6
- package/src/session-runtime/schedule-session-run.mjs +7 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +8 -12
- package/src/session-runtime/services/channel-admin.mjs +66 -18
- package/src/session-runtime/services/channel-worker.mjs +1 -1
- package/src/session-runtime/services/provider-admin.mjs +128 -25
- package/src/session-runtime/services/schedule-access.mjs +14 -0
- package/src/session-runtime/services/usage-dashboard-model.mjs +1 -0
- package/src/session-runtime/session-create/session-options.mjs +7 -4
- package/src/session-runtime/settings-system-api.mjs +16 -1
- package/src/session-runtime/setup-tool/executor.mjs +49 -14
- package/src/session-runtime/setup-tool/extended-actions.mjs +29 -3
- package/src/session-runtime/setup-tool/settings-contract.mjs +65 -4
- package/src/session-runtime/setup-tool/tool-defs.mjs +30 -3
- package/src/session-runtime/task-session-route.mjs +99 -0
- package/src/session-runtime/tool-policy/policy-stage.mjs +5 -2
- package/src/session-runtime/tool-policy/surface-helpers.mjs +2 -3
- package/src/session-runtime/tool-surface.mjs +4 -0
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +98 -21
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -1
- package/src/session-runtime/workspace/transitions.mjs +10 -2
- package/src/standalone/daemon-session-control.mjs +88 -20
- package/src/standalone/daemon.mjs +8 -0
- package/src/standalone/session-protocol.mjs +5 -0
- package/src/standalone/session-service/agent-tree/agent-cancel.mjs +6 -2
- package/src/standalone/session-service/session-calls/session-turns.mjs +6 -0
- package/src/standalone/session-service.mjs +13 -0
- package/src/standalone/session-tool-output.mjs +8 -5
- package/src/tui/app/model-options.mjs +2 -0
- package/src/tui/dist/index.mjs +24 -2
- package/src/tui/session/notice-surface.mjs +17 -1
- package/src/tui/session/session-api/integrations.mjs +1 -0
- package/src/tui/session/session-api/lifecycle.mjs +8 -0
- package/src/tui/session/session-api/settings.mjs +14 -2
- package/src/tui/session/turn-stream.mjs +17 -1
- package/src/tui/session/turn.mjs +22 -2
|
@@ -4,7 +4,15 @@ import { createAgentTaskSessions } from '../session-runtime/agent-task-sessions.
|
|
|
4
4
|
import { assertWorktreeMutationAllowed } from '../session-runtime/services/worktree-access.mjs';
|
|
5
5
|
import { createBotStatus, scanCatalog, scopedBot, sessionBotOf } from '../session-runtime/bot-status.mjs';
|
|
6
6
|
import { handoffContent } from '../session-runtime/handoff-attachments.mjs';
|
|
7
|
-
import {
|
|
7
|
+
import { checkEffort, readModelCatalog } from '../session-runtime/task-session-route.mjs';
|
|
8
|
+
import {
|
|
9
|
+
BOT_HOP_LIMIT_ERROR,
|
|
10
|
+
MAX_BOT_HOPS,
|
|
11
|
+
assertBotChainTarget,
|
|
12
|
+
botChainOf,
|
|
13
|
+
rememberSubmissionBotChain,
|
|
14
|
+
} from '../runtime/shared/bot-hops.mjs';
|
|
15
|
+
import { agentIdOfHomeSession } from '../runtime/agent/orchestrator/session/agent-home.mjs';
|
|
8
16
|
import { rebindDelegation } from '../runtime/agent/orchestrator/session/human-gate/delegation.mjs';
|
|
9
17
|
import { isAgentOnlySession } from '../runtime/agent/orchestrator/session/store-summary-visibility.mjs';
|
|
10
18
|
import { readSessionSidebarState } from '../runtime/shared/session-sidebar-host.mjs';
|
|
@@ -19,7 +27,13 @@ const TARGET_ACTIONS = new Set(['status', 'read', 'send', 'cancel', 'delete']);
|
|
|
19
27
|
const MUTATING_ACTIONS = new Set(['send', 'cancel', 'delete']);
|
|
20
28
|
// The workspace methods `worktree` reaches on another session (all on the
|
|
21
29
|
// session protocol's read/configure allowlists).
|
|
22
|
-
const WORKTREE_METHODS = new Set([
|
|
30
|
+
const WORKTREE_METHODS = new Set([
|
|
31
|
+
'getBranchMenu',
|
|
32
|
+
'requestWorktree',
|
|
33
|
+
'selectBranch',
|
|
34
|
+
'mergeWorkspace',
|
|
35
|
+
'removeWorktree',
|
|
36
|
+
]);
|
|
23
37
|
// Why a send from a conversation that is no bot's reports nothing back.
|
|
24
38
|
const NOT_BOT_REASON = 'only a bot conversation gets session results reported back; check that session for the result';
|
|
25
39
|
|
|
@@ -120,22 +134,25 @@ export function createSessionControl({
|
|
|
120
134
|
}
|
|
121
135
|
}
|
|
122
136
|
// Scoped like bots (`callerScope`, bot-status.mjs): the human's sessions
|
|
123
|
-
// list everything, a bot (or its unattended run)
|
|
124
|
-
//
|
|
137
|
+
// list everything with previews, a bot (or its unattended run) lists the
|
|
138
|
+
// same rows (so it can find a session to send to) but only its own carry
|
|
139
|
+
// the bot, activity, preview and working state, anything else the plain
|
|
140
|
+
// rows without previews. Sub-agent sessions (a
|
|
125
141
|
// parent finds its own through the agent tool) and archived ones (the
|
|
126
142
|
// desktop's flag) only on request; the scan pages past the rows left
|
|
127
143
|
// out, so a page is never short while more rows match.
|
|
128
144
|
const own = scopedBot(context.callerScope);
|
|
129
145
|
const archived = (await readSessionSidebarState())?.archived ?? {};
|
|
130
146
|
const keep = (row) =>
|
|
131
|
-
(!own || sessionBotOf(row) === own) &&
|
|
132
147
|
(args.include_agents === true || !isAgentOnlySession(row)) &&
|
|
133
148
|
(args.include_archived === true || !Object.hasOwn(archived, row.id));
|
|
134
149
|
// Bounded: past 10000 rows scanned with fewer than `limit` matching, the
|
|
135
150
|
// page ends (possibly empty) with a nextCursor to continue from.
|
|
136
151
|
const scan = await scanCatalog(service, keep, { cursor: args.cursor || null, want: limit, maxRows: 10_000 });
|
|
137
152
|
const sessions = scan.rows.map((row) => (Object.hasOwn(archived, row.id) ? { ...row, archived: true } : row));
|
|
138
|
-
return sessionListOutput({ sessions, nextCursor: scan.nextCursor }, limit, {
|
|
153
|
+
return sessionListOutput({ sessions, nextCursor: scan.nextCursor }, limit, {
|
|
154
|
+
rich: own ? (row) => sessionBotOf(row) === own : own !== null,
|
|
155
|
+
});
|
|
139
156
|
}
|
|
140
157
|
if (action === 'send') {
|
|
141
158
|
if (typeof args.prompt !== 'string' || !args.prompt.trim()) {
|
|
@@ -153,6 +170,15 @@ export function createSessionControl({
|
|
|
153
170
|
// its member session is the turn that answers and reports.
|
|
154
171
|
const room = roomIdOfSession(sessionId) ? await roomAddressee(sessionId, args) : null;
|
|
155
172
|
if (room?.sessionId === caller) throw new Error('session: send must target another session');
|
|
173
|
+
// Work goes on to a bot (a room seat's, or a bot's home) only outside
|
|
174
|
+
// the caller's bot chain (`callerBotChain`, bot-chain.mjs), never back
|
|
175
|
+
// to the caller's own bot; the turn it starts carries that chain on.
|
|
176
|
+
const botChain = botChainOf(context.callerBotChain);
|
|
177
|
+
assertBotChainTarget(
|
|
178
|
+
room ? room.agentId : agentIdOfHomeSession(sessionId),
|
|
179
|
+
{ chain: botChain, self: context.callerBotId || context.callerAgentId || '' },
|
|
180
|
+
'session'
|
|
181
|
+
);
|
|
156
182
|
// An agent conversation's send reports its turn back to the agent's
|
|
157
183
|
// home: a task session it created by its stored origin, any other
|
|
158
184
|
// session by an origin for this send only.
|
|
@@ -161,23 +187,58 @@ export function createSessionControl({
|
|
|
161
187
|
const turnSessionId = room ? room.sessionId : sessionId;
|
|
162
188
|
const from = { sessionId: caller, ...(context.callerAgentId ? { agentId: context.callerAgentId } : {}) };
|
|
163
189
|
// The member's turn carries the send's hop (`transcriptMeta.hop`), so a
|
|
164
|
-
// chain through the room counts on toward MAX_BOT_HOPS
|
|
190
|
+
// chain through the room counts on toward MAX_BOT_HOPS; its bot chain is
|
|
191
|
+
// recorded under every submission id that turn may take.
|
|
165
192
|
const submit = room
|
|
166
|
-
? async ({ id, onSettled, follow, transcriptMeta } = {}) =>
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
193
|
+
? async ({ id, onSettled, follow, transcriptMeta } = {}) => {
|
|
194
|
+
rememberSubmissionBotChain(id, transcriptMeta?.botChain);
|
|
195
|
+
const followed = follow
|
|
196
|
+
? (submissionId) => {
|
|
197
|
+
rememberSubmissionBotChain(submissionId, transcriptMeta?.botChain);
|
|
198
|
+
return follow(submissionId);
|
|
199
|
+
}
|
|
200
|
+
: null;
|
|
201
|
+
return {
|
|
202
|
+
accepted:
|
|
203
|
+
(await room.host.ask(sessionId, {
|
|
204
|
+
agentId: room.agentId,
|
|
205
|
+
prompt,
|
|
206
|
+
from,
|
|
207
|
+
...(id ? { id } : {}),
|
|
208
|
+
...(transcriptMeta?.hop > 0 ? { hop: transcriptMeta.hop } : {}),
|
|
209
|
+
...(onSettled ? { onSettled } : {}),
|
|
210
|
+
...(followed ? { follow: followed } : {}),
|
|
211
|
+
})) === true,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
178
214
|
: null;
|
|
179
215
|
const addressed = room ? { to: room.agentId } : {};
|
|
180
216
|
let origin = await agentTasks.reportOrigin(turnSessionId, context.callerAgentId, caller);
|
|
217
|
+
// `effort` is the menu's effort pick on the target: checked now against the
|
|
218
|
+
// levels of its model, applied just before the prompt is queued.
|
|
219
|
+
let applyEffort = async () => {};
|
|
220
|
+
if (args.effort !== undefined) {
|
|
221
|
+
if (room) throw new Error('session: effort applies to a session, not a group room');
|
|
222
|
+
// Any bot, attended or not (`callerBotId`), changes only the task
|
|
223
|
+
// sessions it created; reports go only to a bot conversation.
|
|
224
|
+
const botId = context.callerBotId || context.callerAgentId || '';
|
|
225
|
+
if (botId && (await agentTasks.reportOrigin(sessionId, botId, caller))?.target === 'session') {
|
|
226
|
+
throw new Error('session: a bot may set the effort only of the task sessions it created');
|
|
227
|
+
}
|
|
228
|
+
const target = (await service.readSession({ sessionId, transcriptItemLimit: 1 })).full;
|
|
229
|
+
if (!target) throw new Error(`session: ${sessionId} is unavailable`);
|
|
230
|
+
const level = checkEffort(args.effort, target, await readModelCatalog(service, caller));
|
|
231
|
+
// sessionOnly: the session's route changes, never the user's saved model settings.
|
|
232
|
+
applyEffort = async () => {
|
|
233
|
+
const applied = await service.configureSession(
|
|
234
|
+
{ sessionId, action: 'setEffort', args: [level, { sessionOnly: true }] },
|
|
235
|
+
{ revision: 1 }
|
|
236
|
+
);
|
|
237
|
+
if (applied?.value === false) {
|
|
238
|
+
throw new Error(`session: ${sessionId} is busy with another command; effort not changed`);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
181
242
|
// A send made in a bot-descended turn (`callerHop` > 0), or by a bot to
|
|
182
243
|
// another session, extends the chain by one; a bot's own task session
|
|
183
244
|
// only continues it. Refused past the limit, whatever the target.
|
|
@@ -189,13 +250,17 @@ export function createSessionControl({
|
|
|
189
250
|
if (origin) {
|
|
190
251
|
const hop = chained(origin.target === 'session' ? turnHop + 1 : turnHop);
|
|
191
252
|
if (hop > 0) origin = { ...origin, hop };
|
|
253
|
+
if (botChain.length) origin = { ...origin, botChain };
|
|
192
254
|
// The task session a bot created acts as the sending conversation does
|
|
193
255
|
// from now on (attended chat or run); another's session keeps its own.
|
|
194
256
|
if (origin.target !== 'session') await rebindDelegation(sessionId, caller);
|
|
257
|
+
await applyEffort();
|
|
195
258
|
const sent = await agentTasks.send(turnSessionId, prompt, origin, submit);
|
|
196
259
|
return { sessionId, ...addressed, ...sent, ...(hop > 0 ? { hop } : {}) };
|
|
197
260
|
}
|
|
198
261
|
const hop = turnHop > 0 ? chained(turnHop + 1) : 0;
|
|
262
|
+
await applyEffort();
|
|
263
|
+
const chainMeta = { ...(hop > 0 ? { hop } : {}), ...(botChain.length ? { botChain } : {}) };
|
|
199
264
|
let accepted;
|
|
200
265
|
if (submit) accepted = (await submit(hop > 0 ? { transcriptMeta: { hop } } : {})).accepted;
|
|
201
266
|
else {
|
|
@@ -204,7 +269,7 @@ export function createSessionControl({
|
|
|
204
269
|
const result = await service.submitSession({
|
|
205
270
|
sessionId,
|
|
206
271
|
prompt,
|
|
207
|
-
options: { mode: 'prompt', transcriptMeta: { sender: 'lead', ...
|
|
272
|
+
options: { mode: 'prompt', transcriptMeta: { sender: 'lead', ...chainMeta } },
|
|
208
273
|
});
|
|
209
274
|
accepted = result.accepted === true;
|
|
210
275
|
}
|
|
@@ -256,6 +321,9 @@ export function createSessionControl({
|
|
|
256
321
|
cwd: snapshot.cwd ?? null,
|
|
257
322
|
provider: snapshot.provider ?? null,
|
|
258
323
|
model: snapshot.model ?? null,
|
|
324
|
+
effort: snapshot.effort ?? null,
|
|
325
|
+
autoEffort: snapshot.autoEffort ?? null,
|
|
326
|
+
fast: snapshot.fast ?? null,
|
|
259
327
|
busy: snapshot.busy === true,
|
|
260
328
|
quotaResume: snapshot.quotaResume ?? null,
|
|
261
329
|
};
|
|
@@ -1001,6 +1001,8 @@ async function main() {
|
|
|
1001
1001
|
setAgentHomeLiveSurface({
|
|
1002
1002
|
busy: (sessionId) => sessionService.liveSessionBusy(sessionId),
|
|
1003
1003
|
notice: (sessionId, notice) => sessionService.pushLiveSessionNotice(sessionId, notice),
|
|
1004
|
+
// A bot's emoji reaction on a user message (message-reactions.mjs).
|
|
1005
|
+
react: (sessionId, messageId, reactions) => sessionService.setLiveMessageReactions(sessionId, messageId, reactions),
|
|
1004
1006
|
// A task session's result (session create): the agent relays it in one
|
|
1005
1007
|
// turn. Resolves false when the turn was refused, else `{ settled }`, the
|
|
1006
1008
|
// turn's end status ('done' when it finished).
|
|
@@ -1027,6 +1029,12 @@ async function main() {
|
|
|
1027
1029
|
(await sessionService.submitSession({ sessionId, prompt, options }))?.accepted === true,
|
|
1028
1030
|
abort: (sessionId) => sessionService.abortSession({ sessionId }),
|
|
1029
1031
|
});
|
|
1032
|
+
// Mixy's 30-minute heartbeat check (mixy-heartbeat.mjs), on an unref'd timer.
|
|
1033
|
+
import('../session-runtime/mixy-heartbeat.mjs')
|
|
1034
|
+
.then(({ startMixyHeartbeat }) =>
|
|
1035
|
+
startMixyHeartbeat({ getSessionService: () => sessionService, log: (line) => log(`heartbeat: ${line}`) })
|
|
1036
|
+
)
|
|
1037
|
+
.catch((error) => log(`heartbeat start failed: ${error?.message || error}`));
|
|
1030
1038
|
// Results queued before a restart are posted now; results a finished run
|
|
1031
1039
|
// could not queue (kept on its routine's record) are queued again.
|
|
1032
1040
|
recoverScheduleResults().catch((error) => log(`routine results recovery failed: ${error?.message || error}`));
|
|
@@ -47,6 +47,8 @@ export const SESSION_READ_ACTIONS = Object.freeze([
|
|
|
47
47
|
'getQuotaHistory',
|
|
48
48
|
'getRecapSettings',
|
|
49
49
|
'getSessionTitleSettings',
|
|
50
|
+
'getHeartbeatSettings',
|
|
51
|
+
'getReactionSettings',
|
|
50
52
|
'getLeadAppearance',
|
|
51
53
|
'getBotOrder',
|
|
52
54
|
'getWebSearchRoute',
|
|
@@ -134,6 +136,7 @@ export const SESSION_CONFIGURE_ACTIONS = Object.freeze([
|
|
|
134
136
|
'deleteMediaAssets',
|
|
135
137
|
'deleteSchedule',
|
|
136
138
|
'saveCustomProvider',
|
|
139
|
+
'saveCustomProviderKey',
|
|
137
140
|
'removeCustomProvider',
|
|
138
141
|
'updateCustomProviderModel',
|
|
139
142
|
'setModelPreferences',
|
|
@@ -238,6 +241,8 @@ export const SESSION_CONFIGURE_ACTIONS = Object.freeze([
|
|
|
238
241
|
'setProgressHint',
|
|
239
242
|
'setRecapEnabled',
|
|
240
243
|
'setAiSessionTitles',
|
|
244
|
+
'setHeartbeatEnabled',
|
|
245
|
+
'setReactionsEnabled',
|
|
241
246
|
'setLeadAppearance',
|
|
242
247
|
'setBotOrder',
|
|
243
248
|
'setRoute',
|
|
@@ -64,9 +64,10 @@ export function createAgentCancellation({
|
|
|
64
64
|
if (typeof closeCanonical !== 'function') {
|
|
65
65
|
throw new TypeError('session runtime must implement closeCanonicalSession');
|
|
66
66
|
}
|
|
67
|
+
const wasRunning = descriptor.status === 'running';
|
|
67
68
|
// The close clears the transcript: capture a running turn's transcript
|
|
68
69
|
// first so the cancelled turn still hands off its partial output.
|
|
69
|
-
if (
|
|
70
|
+
if (wasRunning) {
|
|
70
71
|
try {
|
|
71
72
|
const { messages } = await entry.runtime.readModelMessages(0);
|
|
72
73
|
rememberCancelTranscript(id, messages);
|
|
@@ -77,7 +78,10 @@ export function createAgentCancellation({
|
|
|
77
78
|
const closed = await closeCanonical.call(entry.runtime, reason, { tombstone: options.tombstone === true });
|
|
78
79
|
if (closed !== true) throw new Error(`agent session ${id} could not be durably closed`);
|
|
79
80
|
descriptor.closed = options.tombstone === true;
|
|
80
|
-
|
|
81
|
+
// Only a close that stopped a running turn is a cancellation; closing a
|
|
82
|
+
// finished agent (cleanup after its handoff) keeps its last status.
|
|
83
|
+
if (descriptor.closed) descriptor.status = 'closed';
|
|
84
|
+
else if (wasRunning) descriptor.status = 'cancelled';
|
|
81
85
|
descriptor.stage = descriptor.status;
|
|
82
86
|
descriptor.updatedAt = Date.now();
|
|
83
87
|
return true;
|
|
@@ -128,6 +128,12 @@ export function createSessionTurnCalls(ctx) {
|
|
|
128
128
|
const accepted = await Promise.resolve(target.call(entry.runtime, intake.prompt, submissionOptions));
|
|
129
129
|
if (accepted === true) {
|
|
130
130
|
entry.reservedOnly = false;
|
|
131
|
+
if (isUserTurn(submissionOptions)) {
|
|
132
|
+
// The user is active: Mixy's due heartbeat check waits for quiet.
|
|
133
|
+
void import('../../../session-runtime/mixy-heartbeat.mjs')
|
|
134
|
+
.then(({ noteUserTurn }) => noteUserTurn())
|
|
135
|
+
.catch((error) => log(`heartbeat activity note failed: ${error?.message || error}`));
|
|
136
|
+
}
|
|
131
137
|
if (isAgentHomeSessionId(id) && isUserTurn(submissionOptions)) {
|
|
132
138
|
// The user spoke in this agent's home: its round trips with other
|
|
133
139
|
// sessions count from zero again, so a capped thread wakes again.
|
|
@@ -274,6 +274,18 @@ export function createSessionService({
|
|
|
274
274
|
return true;
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
+
/** Show the reactions now on message `messageId` in the hosted runtime of
|
|
278
|
+
* `sessionId` and publish them to its views; false when no runtime hosts
|
|
279
|
+
* it or its live rows do not hold that message. */
|
|
280
|
+
function setLiveMessageReactions(sessionId, messageId, reactions) {
|
|
281
|
+
const owner = projection.sessionOwner(sessionId);
|
|
282
|
+
if (typeof owner?.runtime?.setMessageReactions !== 'function') return false;
|
|
283
|
+
if (!owner.runtime.setMessageReactions(messageId, reactions)) return false;
|
|
284
|
+
const step = projection.advance(owner);
|
|
285
|
+
if (step.changed) projection.publishStep(owner, step);
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
|
|
277
289
|
function getStatus() {
|
|
278
290
|
const busy = retention.liveBusyCount();
|
|
279
291
|
let watched = 0;
|
|
@@ -322,6 +334,7 @@ export function createSessionService({
|
|
|
322
334
|
liveSessionBusy,
|
|
323
335
|
liveSessionAwaitingApproval,
|
|
324
336
|
pushLiveSessionNotice,
|
|
337
|
+
setLiveMessageReactions,
|
|
325
338
|
stop,
|
|
326
339
|
startQuotaResume: () => quotaResume?.start(),
|
|
327
340
|
runDueQuotaResumes: () => quotaResume?.tick() ?? Promise.resolve(),
|
|
@@ -13,20 +13,23 @@ const outputBytes = (value) => Buffer.byteLength(JSON.stringify(value), 'utf8');
|
|
|
13
13
|
|
|
14
14
|
/** Keep catalog paging authoritative even when the output budget shortens a page.
|
|
15
15
|
* `rich` adds the owning bot, last activity, latest line and working state
|
|
16
|
-
* (never for a background caller: bot-status.mjs scopedBot).
|
|
16
|
+
* (never for a background caller: bot-status.mjs scopedBot). `rich` may be a
|
|
17
|
+
* per-row predicate: a bot gets them only for its own sessions. */
|
|
17
18
|
export function sessionListOutput(page, limit, { rich = true } = {}) {
|
|
18
19
|
const raw = page.sessions;
|
|
19
20
|
const sessions = raw.slice(0, limit).map((row) => {
|
|
21
|
+
// Decided first: a session that is not the caller's carries its real
|
|
22
|
+
// title only, never a preview standing in for it.
|
|
23
|
+
const own = typeof rich === 'function' ? rich(row) : rich;
|
|
24
|
+
const title = own ? row.title || row.preview : row.title;
|
|
20
25
|
const base = {
|
|
21
26
|
id: row.id,
|
|
22
|
-
...(
|
|
23
|
-
? { title: escapeHarnessTags(truncateToolText(row.title || row.preview, 160)) }
|
|
24
|
-
: {}),
|
|
27
|
+
...(title ? { title: escapeHarnessTags(truncateToolText(title, 160)) } : {}),
|
|
25
28
|
...(row.status ? { status: row.status } : {}),
|
|
26
29
|
...(row.cwd ? { cwd: row.cwd } : {}),
|
|
27
30
|
...(row.archived === true ? { archived: true } : {}),
|
|
28
31
|
};
|
|
29
|
-
if (!
|
|
32
|
+
if (!own) return base;
|
|
30
33
|
// The bot it belongs to: its chat, room seat or the task's creator.
|
|
31
34
|
const bot = sessionBotOf(row);
|
|
32
35
|
const lastUsedAt = Number(row.lastUsedAt) || 0;
|
|
@@ -173,6 +173,7 @@ const PROVIDER_DISPLAY_NAMES = new Map([
|
|
|
173
173
|
['command-code-oauth', 'Command Code'],
|
|
174
174
|
['mimo-oauth', 'MiMo'],
|
|
175
175
|
['cursor-oauth', 'Cursor OAuth'],
|
|
176
|
+
['devin-oauth', 'Devin'],
|
|
176
177
|
['antigravity-oauth', 'Antigravity OAuth'],
|
|
177
178
|
['openai', 'OpenAI API'],
|
|
178
179
|
['openai-api', 'OpenAI API'],
|
|
@@ -279,6 +280,7 @@ const PROVIDER_DISPLAY_RANKS = {
|
|
|
279
280
|
'mimo-oauth': 34,
|
|
280
281
|
'antigravity-oauth': 31,
|
|
281
282
|
'cursor-oauth': 32,
|
|
283
|
+
'devin-oauth': 32.5,
|
|
282
284
|
'opencode-go-oauth': 34.5,
|
|
283
285
|
openai: 40,
|
|
284
286
|
'openai-api': 40,
|
package/src/tui/dist/index.mjs
CHANGED
|
@@ -51347,6 +51347,7 @@ var PROVIDER_CATALOGS = Object.freeze({
|
|
|
51347
51347
|
rosterLimitsAuthoritative: true
|
|
51348
51348
|
},
|
|
51349
51349
|
"cursor-oauth": { relay: true },
|
|
51350
|
+
"devin-oauth": { relay: true },
|
|
51350
51351
|
"cursor-api": { relay: true },
|
|
51351
51352
|
// The models.dev listing id of the Go gateway: a relay catalog, not a
|
|
51352
51353
|
// selectable provider (the model lookup needs it flagged as a relay).
|
|
@@ -59670,6 +59671,7 @@ var PROVIDER_DISPLAY_NAMES = /* @__PURE__ */ new Map([
|
|
|
59670
59671
|
["command-code-oauth", "Command Code"],
|
|
59671
59672
|
["mimo-oauth", "MiMo"],
|
|
59672
59673
|
["cursor-oauth", "Cursor OAuth"],
|
|
59674
|
+
["devin-oauth", "Devin"],
|
|
59673
59675
|
["antigravity-oauth", "Antigravity OAuth"],
|
|
59674
59676
|
["openai", "OpenAI API"],
|
|
59675
59677
|
["openai-api", "OpenAI API"],
|
|
@@ -59754,6 +59756,7 @@ var PROVIDER_DISPLAY_RANKS = {
|
|
|
59754
59756
|
"mimo-oauth": 34,
|
|
59755
59757
|
"antigravity-oauth": 31,
|
|
59756
59758
|
"cursor-oauth": 32,
|
|
59759
|
+
"devin-oauth": 32.5,
|
|
59757
59760
|
"opencode-go-oauth": 34.5,
|
|
59758
59761
|
openai: 40,
|
|
59759
59762
|
"openai-api": 40,
|
|
@@ -62131,12 +62134,30 @@ function markUserDataInitialized(dataDir2) {
|
|
|
62131
62134
|
}
|
|
62132
62135
|
var ensuredDataDirs = /* @__PURE__ */ new Set();
|
|
62133
62136
|
function ensureUserDataInitialized(dataDir2) {
|
|
62134
|
-
if (!dataDir2) return;
|
|
62137
|
+
if (!dataDir2 || skipBackups()) return;
|
|
62135
62138
|
const key = normalizeDataDir(dataDir2);
|
|
62136
62139
|
if (ensuredDataDirs.has(key)) return;
|
|
62137
|
-
if (!
|
|
62140
|
+
if (!hasPublishedBackup(dataDir2)) {
|
|
62141
|
+
backupUserData(dataDir2, "initial-snapshot");
|
|
62142
|
+
if (!hasPublishedBackup(dataDir2)) return;
|
|
62143
|
+
}
|
|
62138
62144
|
ensuredDataDirs.add(key);
|
|
62139
62145
|
}
|
|
62146
|
+
function hasPublishedBackup(dataDir2) {
|
|
62147
|
+
const instanceId = readInitInstanceId(dataDir2);
|
|
62148
|
+
if (!instanceId) return false;
|
|
62149
|
+
const key = normalizeDataDir(dataDir2);
|
|
62150
|
+
const scopeDir = scopeDirFor(dataDir2);
|
|
62151
|
+
return listBackupDirsNewestFirst(scopeDir).some((name) => {
|
|
62152
|
+
try {
|
|
62153
|
+
const manifest = JSON.parse(readFileSync10(manifestPath(scopeDir, name), "utf8"));
|
|
62154
|
+
return typeof manifest?.dataDir === "string" && normalizeDataDir(manifest.dataDir) === key && manifest.instanceId === instanceId;
|
|
62155
|
+
} catch (error) {
|
|
62156
|
+
if (isAbsentBackupError(error)) return false;
|
|
62157
|
+
throw error;
|
|
62158
|
+
}
|
|
62159
|
+
});
|
|
62160
|
+
}
|
|
62140
62161
|
function skipBackups() {
|
|
62141
62162
|
return process.env.MIXDOG_SKIP_USER_DATA_BACKUP === "1" || process.env.MIXDOG_SKIP_USER_DATA_BACKUP === "true";
|
|
62142
62163
|
}
|
|
@@ -62270,6 +62291,7 @@ var {
|
|
|
62270
62291
|
setSecret: _setSecret,
|
|
62271
62292
|
deleteSecret: _deleteSecret,
|
|
62272
62293
|
hasSecret: _hasSecret,
|
|
62294
|
+
hasFallbackSecret: _hasFallbackSecret,
|
|
62273
62295
|
setSecretAsync: _setSecretAsyncImpl,
|
|
62274
62296
|
deleteSecretAsync: _deleteSecretAsyncImpl,
|
|
62275
62297
|
keychainStatus: _keychainStatus,
|
|
@@ -35,10 +35,26 @@ export function createNoticeSurface({ getState, set, isDisposed, pushItem, repla
|
|
|
35
35
|
// A routine result is a finished sentence, never a raw error to polish;
|
|
36
36
|
// a group-room row is someone's words, kept verbatim (a pass has none).
|
|
37
37
|
const value =
|
|
38
|
-
options.routine || options.room || options.heldRequest
|
|
38
|
+
options.routine || options.room || options.heldRequest || options.assistant
|
|
39
|
+
? String(text ?? '').trim()
|
|
40
|
+
: polishNoticeText(text);
|
|
39
41
|
if (!value && !options.room) return null;
|
|
40
42
|
const forceTranscript = options.transcript === true;
|
|
41
43
|
if (!forceTranscript) return pushToast(value, tone, options.ttlMs, options);
|
|
44
|
+
// The agent's own message posted outside a turn (agent-home-delivery.mjs):
|
|
45
|
+
// its reply row, as the stored message restores.
|
|
46
|
+
if (options.assistant) {
|
|
47
|
+
const id = options.messageId || nextId();
|
|
48
|
+
pushItem({
|
|
49
|
+
kind: 'assistant',
|
|
50
|
+
id,
|
|
51
|
+
text: value,
|
|
52
|
+
streaming: false,
|
|
53
|
+
at: Date.now(),
|
|
54
|
+
...(options.messageId ? { messageId: options.messageId } : {}),
|
|
55
|
+
});
|
|
56
|
+
return id;
|
|
57
|
+
}
|
|
42
58
|
const userRow = options.room?.kind === 'user';
|
|
43
59
|
// A room user row keeps its message id (the sender's submission id), as a
|
|
44
60
|
// turn's own prompt row keeps its submission id.
|
|
@@ -138,6 +138,7 @@ export function createSessionIntegrationsApi(bag, { oauthFlows }) {
|
|
|
138
138
|
(input) => runtime.saveCustomProvider(input),
|
|
139
139
|
(result) => `custom provider saved: ${result.id}`
|
|
140
140
|
),
|
|
141
|
+
saveCustomProviderKey: (id, secret) => runtime.saveCustomProviderKey(id, secret),
|
|
141
142
|
updateCustomProviderModel: (id, modelId, patch) => runtime.updateCustomProviderModel(id, modelId, patch),
|
|
142
143
|
setModelPreferences: (provider, changes) => runtime.setModelPreferences(provider, changes),
|
|
143
144
|
removeCustomProvider: async (id) => {
|
|
@@ -81,6 +81,14 @@ export function createSessionLifecycleApi(bag, { restoreTranscriptItems, oauthFl
|
|
|
81
81
|
pushNotice,
|
|
82
82
|
removeNotice,
|
|
83
83
|
setProgressHint,
|
|
84
|
+
// A bot's emoji reactions on a user message (message-reactions.mjs): its
|
|
85
|
+
// live row shows them at once; false when the live rows do not hold it.
|
|
86
|
+
setMessageReactions: (messageId, reactions) => {
|
|
87
|
+
const row = bag
|
|
88
|
+
.getState()
|
|
89
|
+
.items.findLast((item) => item?.kind === 'user' && messageId && item.messageId === messageId);
|
|
90
|
+
return row ? bag.patchItem(row.id, { reactions: Array.isArray(reactions) ? reactions : [] }) : false;
|
|
91
|
+
},
|
|
84
92
|
...createSessionResetActions(bag),
|
|
85
93
|
listSessions: (options) => {
|
|
86
94
|
return runtime.listSessions(options);
|
|
@@ -34,8 +34,8 @@ export function createSessionSettingsApi(bag) {
|
|
|
34
34
|
// refused: another command's lock does not block a selection.
|
|
35
35
|
setModel: async (m) => (await bag.setSessionRoute({ model: m })) !== false,
|
|
36
36
|
setEffort: withCommandLock(
|
|
37
|
-
async (value) => {
|
|
38
|
-
await runtime.setEffort(value);
|
|
37
|
+
async (value, options) => {
|
|
38
|
+
await runtime.setEffort(value, options);
|
|
39
39
|
set({ ...routeState() });
|
|
40
40
|
return runtime.effort || 'auto';
|
|
41
41
|
},
|
|
@@ -112,6 +112,18 @@ export function createSessionSettingsApi(bag) {
|
|
|
112
112
|
setAiSessionTitles: withCommandLock(async (enabled) => {
|
|
113
113
|
return await runtime.setAiSessionTitles?.(enabled);
|
|
114
114
|
}),
|
|
115
|
+
getHeartbeatSettings: () => {
|
|
116
|
+
return runtime.getHeartbeatSettings?.() || { heartbeat: true };
|
|
117
|
+
},
|
|
118
|
+
setHeartbeatEnabled: withCommandLock(async (enabled) => {
|
|
119
|
+
return await runtime.setHeartbeatEnabled?.(enabled);
|
|
120
|
+
}),
|
|
121
|
+
getReactionSettings: () => {
|
|
122
|
+
return runtime.getReactionSettings?.() || { reactions: true };
|
|
123
|
+
},
|
|
124
|
+
setReactionsEnabled: withCommandLock(async (enabled) => {
|
|
125
|
+
return await runtime.setReactionsEnabled?.(enabled);
|
|
126
|
+
}),
|
|
115
127
|
getLeadAppearance: () => {
|
|
116
128
|
return runtime.getLeadAppearance?.() || { avatar: null };
|
|
117
129
|
},
|
|
@@ -40,6 +40,9 @@ export function createTurnStream(deps) {
|
|
|
40
40
|
// (`commentary` / `final_answer`), or null; the desktop row carries it.
|
|
41
41
|
phase: null,
|
|
42
42
|
thinkingText: '',
|
|
43
|
+
// Length of thinkingText when the current provider send began: a
|
|
44
|
+
// `reasoning` retraction drops only what that send added.
|
|
45
|
+
thinkingSendMark: 0,
|
|
43
46
|
thinkingStartedAt: 0,
|
|
44
47
|
thinkingSegmentStartedAt: 0,
|
|
45
48
|
accumulatedThinkingMs: 0,
|
|
@@ -492,9 +495,20 @@ export function appendReasoningDelta(stream, chunk) {
|
|
|
492
495
|
scheduleStreamFlush(stream);
|
|
493
496
|
}
|
|
494
497
|
|
|
498
|
+
// A provider send (one model request of the turn) begins: the thinking so far
|
|
499
|
+
// belongs to earlier sends and survives a retraction of this one.
|
|
500
|
+
export function markThinkingSendStart(stream) {
|
|
501
|
+
stream.thinkingSendMark = stream.thinkingText.length;
|
|
502
|
+
}
|
|
503
|
+
|
|
495
504
|
export function resetStreamedText(stream, { chars, reasoning } = {}) {
|
|
496
505
|
const count = Math.max(0, Number(chars) || 0);
|
|
497
|
-
if (reasoning)
|
|
506
|
+
if (reasoning) {
|
|
507
|
+
endThinkingForText(stream);
|
|
508
|
+
// The discarded attempt's thinking must not prefix the next attempt's;
|
|
509
|
+
// earlier sends' thinking stays.
|
|
510
|
+
stream.thinkingText = stream.thinkingText.slice(0, stream.thinkingSendMark);
|
|
511
|
+
}
|
|
498
512
|
if (!count) return reasoning === true;
|
|
499
513
|
flushStreamBatch(stream);
|
|
500
514
|
stream.assistantText = stream.assistantText.slice(0, Math.max(0, stream.assistantText.length - count));
|
|
@@ -529,6 +543,8 @@ export function resetStreamedText(stream, { chars, reasoning } = {}) {
|
|
|
529
543
|
// tool phase (`spinnerMode`: 'task-wait' or 'tool-use').
|
|
530
544
|
export function closeThinkingForToolBatch(stream, spinnerMode) {
|
|
531
545
|
const { getState, set } = stream;
|
|
546
|
+
// The send that produced this batch is complete; its thinking is kept.
|
|
547
|
+
markThinkingSendStart(stream);
|
|
532
548
|
if (stream.thinkingText && getState().thinking) {
|
|
533
549
|
const thinkingLastEndedAt = closeThinkingSegment(stream);
|
|
534
550
|
set({
|
package/src/tui/session/turn.mjs
CHANGED
|
@@ -23,6 +23,10 @@ import { isRejected429 } from '../../runtime/shared/error-presentation.mjs';
|
|
|
23
23
|
import { QUOTA_KINDS, providerErrorKind } from '../../runtime/agent/orchestrator/providers/provider-error-kind.mjs';
|
|
24
24
|
import { turnFailureRecord } from '../../runtime/agent/orchestrator/session/turn-failure.mjs';
|
|
25
25
|
import { registerTaskWaitSteeringCheck } from '../../runtime/agent/orchestrator/session/task-wait-control.mjs';
|
|
26
|
+
import {
|
|
27
|
+
isReactionSuccess,
|
|
28
|
+
reactionCallIds,
|
|
29
|
+
} from '../../runtime/agent/orchestrator/runtime-core/reaction-tool-defs.mjs';
|
|
26
30
|
import { promptDisplayText, STEERING_SUPPRESSED_DISPLAY } from './queue-helpers.mjs';
|
|
27
31
|
import { yieldToRenderer } from './render-timing.mjs';
|
|
28
32
|
import { archiveItemsForCompaction, compactionArchivedRows } from './compaction-view.mjs';
|
|
@@ -35,6 +39,7 @@ import {
|
|
|
35
39
|
appendTextDelta,
|
|
36
40
|
appendAssistantText,
|
|
37
41
|
appendReasoningDelta,
|
|
42
|
+
markThinkingSendStart,
|
|
38
43
|
noteAssistantPhase,
|
|
39
44
|
resetStreamedText,
|
|
40
45
|
applyStageChange,
|
|
@@ -152,6 +157,9 @@ function beginTurn({ flags, getState, transcriptRouteMetadata }, options) {
|
|
|
152
157
|
askResult: null,
|
|
153
158
|
finishedNormally: false,
|
|
154
159
|
transcriptCompacted: false,
|
|
160
|
+
// Ids of this turn's react calls; `reacted` is set once one succeeds.
|
|
161
|
+
reactionCallIds: new Set(),
|
|
162
|
+
reacted: false,
|
|
155
163
|
};
|
|
156
164
|
}
|
|
157
165
|
|
|
@@ -403,10 +411,15 @@ export function toolHandlers(ctx) {
|
|
|
403
411
|
if (isCurrentTurn()) settleSegmentBeforeToolCall(ctx);
|
|
404
412
|
},
|
|
405
413
|
onToolCall: async (_iter, calls) => {
|
|
406
|
-
if (isCurrentTurn())
|
|
414
|
+
if (!isCurrentTurn()) return;
|
|
415
|
+
for (const id of reactionCallIds(calls)) ctx.turn.reactionCallIds.add(id);
|
|
416
|
+
await openToolCalls(ctx, calls);
|
|
407
417
|
},
|
|
408
418
|
onToolResult: (message) => {
|
|
409
419
|
if (!isCurrentTurn()) return;
|
|
420
|
+
// A reaction that landed on the user's message is this turn's output
|
|
421
|
+
// even with no reply text; a rejected or failed react call is not.
|
|
422
|
+
if (isReactionSuccess(message, ctx.turn.reactionCallIds)) ctx.turn.reacted = true;
|
|
410
423
|
try {
|
|
411
424
|
options.onToolResult?.(message);
|
|
412
425
|
} catch {}
|
|
@@ -440,7 +453,9 @@ export function streamHandlers(ctx) {
|
|
|
440
453
|
const { isCurrentTurn, stream } = ctx;
|
|
441
454
|
return {
|
|
442
455
|
onProviderSendStarted: () => {
|
|
443
|
-
if (isCurrentTurn())
|
|
456
|
+
if (!isCurrentTurn()) return;
|
|
457
|
+
markThinkingSendStart(stream);
|
|
458
|
+
markLiveCallRequestStart(stream);
|
|
444
459
|
},
|
|
445
460
|
onStageChange: async (stage, detail = null) => {
|
|
446
461
|
if (!isCurrentTurn()) return;
|
|
@@ -554,6 +569,9 @@ export function turnDoneItem({ turn, stream, cards, getState, nextId }) {
|
|
|
554
569
|
outputTokens: finalOutputTokens(stream, getState().spinner?.outputTokens),
|
|
555
570
|
thinkingElapsedMs: stream.thinkingStartedAt ? stream.accumulatedThinkingMs : 0,
|
|
556
571
|
toolCount: cards.toolCards.length,
|
|
572
|
+
// Its answer may be a reaction alone: readers of the turn's final answer
|
|
573
|
+
// take it as done with nothing to say (session-final-answer.ts).
|
|
574
|
+
...(turn.reacted ? { reacted: true } : {}),
|
|
557
575
|
verb: turn.completionVerb,
|
|
558
576
|
at: Date.now(),
|
|
559
577
|
...(turn.failureDetail ? { detail: turn.failureDetail } : {}),
|
|
@@ -577,9 +595,11 @@ function publishTurnClose(ctx, closingItems) {
|
|
|
577
595
|
const assistantOutput = (stream.currentAssistantText || stream.assistantText || '').trim();
|
|
578
596
|
// Suppress only true pending-resume no-ops: no transcript items added and
|
|
579
597
|
// no model output; cancelled/error turns and any visible turn stay marked.
|
|
598
|
+
// A turn whose only output was a reaction ends with its turndone row too.
|
|
580
599
|
const isNoOpTurn =
|
|
581
600
|
turn.finishedNormally &&
|
|
582
601
|
!turn.cancelled &&
|
|
602
|
+
!turn.reacted &&
|
|
583
603
|
cards.toolCards.length === 0 &&
|
|
584
604
|
!resultContent &&
|
|
585
605
|
!assistantOutput &&
|