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
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// react: a bot's emoji reaction on the user's message, shown under the user's
|
|
2
|
+
// bubble the way a messenger shows one. Offered only in a bot's own chat with
|
|
3
|
+
// the user (its home chat or its seat in a group room) while the Reactions
|
|
4
|
+
// setting is on (session-tool-surface.mjs, builtin-features.mjs).
|
|
5
|
+
export const REACTION_TOOL_NAME = 'react';
|
|
6
|
+
|
|
7
|
+
/** The reactions a bot can leave, in the order the schema lists them. */
|
|
8
|
+
export const REACTION_EMOJI = Object.freeze(['❤️', '👍', '✔️', '😆', '😮', '😢', '🙏']);
|
|
9
|
+
const MESSAGES_BACK_MAX = 20;
|
|
10
|
+
|
|
11
|
+
export const REACTION_TOOL_DEFS = Object.freeze([
|
|
12
|
+
{
|
|
13
|
+
name: REACTION_TOOL_NAME,
|
|
14
|
+
title: 'React',
|
|
15
|
+
trigger: "Leave one emoji reaction on the user's message, as a chat app shows one under it.",
|
|
16
|
+
description:
|
|
17
|
+
"Leave one emoji reaction under the user's message. Two uses only. Instead of a reply: when the message needs no words back (ok, got it, agreement, thanks, a done update), react alone with no reply text, when the reaction fully answers, instead of a filler reply like 'sounds good'. As emphasis: on clear good or sad news or something truly funny, react and still reply as the message needs; keep this rare, about once per 5-10 exchanges. Otherwise none: never react to questions or requests needing real content, never on two messages in a row, never as a status signal, and never mention or explain a reaction. ❤️ warmth or good news, 👍 agreement or ok, ✔️ done or noted, 😆 funny, 😮 surprising, 😢 sad, 🙏 thanks. In a group room, react only to messages for you, never repeating one another member left. Reacting again replaces yours.",
|
|
18
|
+
annotations: {
|
|
19
|
+
title: 'React',
|
|
20
|
+
readOnlyHint: false,
|
|
21
|
+
destructiveHint: false,
|
|
22
|
+
idempotentHint: true,
|
|
23
|
+
openWorldHint: false,
|
|
24
|
+
agentHidden: true,
|
|
25
|
+
},
|
|
26
|
+
inputSchema: {
|
|
27
|
+
type: 'object',
|
|
28
|
+
properties: {
|
|
29
|
+
emoji: { type: 'string', description: `One of ${REACTION_EMOJI.join(' ')}.` },
|
|
30
|
+
messages_back: {
|
|
31
|
+
type: 'integer',
|
|
32
|
+
minimum: 0,
|
|
33
|
+
maximum: MESSAGES_BACK_MAX,
|
|
34
|
+
description: "0 or omitted: the user's latest message; 1: the one before it, and so on.",
|
|
35
|
+
},
|
|
36
|
+
remove: { type: 'boolean', description: 'Remove your reaction instead.' },
|
|
37
|
+
},
|
|
38
|
+
additionalProperties: false,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const bare = (text) => text.replace(/\uFE0F/g, '');
|
|
44
|
+
|
|
45
|
+
/** The reaction `value` names (a missing or extra U+FE0F tolerated), or ''. */
|
|
46
|
+
export function reactionEmoji(value) {
|
|
47
|
+
const text = typeof value === 'string' ? bare(value.trim()) : '';
|
|
48
|
+
return (text && REACTION_EMOJI.find((emoji) => bare(emoji) === text)) || '';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** `{ emoji, messagesBack }` of a react call (`emoji` '' removes); throws on
|
|
52
|
+
* anything else. */
|
|
53
|
+
export function validateReactionArgs(args) {
|
|
54
|
+
const input = args && typeof args === 'object' ? args : {};
|
|
55
|
+
const back = input.messages_back ?? 0;
|
|
56
|
+
if (!Number.isInteger(back) || back < 0 || back > MESSAGES_BACK_MAX) {
|
|
57
|
+
throw new Error(`react: messages_back is a whole number from 0 to ${MESSAGES_BACK_MAX}`);
|
|
58
|
+
}
|
|
59
|
+
if (input.remove === true) return { emoji: '', messagesBack: back };
|
|
60
|
+
const emoji = reactionEmoji(input.emoji);
|
|
61
|
+
if (!emoji) throw new Error(`react: emoji must be exactly one of ${REACTION_EMOJI.join(' ')}`);
|
|
62
|
+
return { emoji, messagesBack: back };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Whether a provider tool call (direct, or bridged through call_tool) is a react call. */
|
|
66
|
+
function isReactionToolCall(call) {
|
|
67
|
+
const name = call?.name ?? call?.function?.name;
|
|
68
|
+
if (name === REACTION_TOOL_NAME) return true;
|
|
69
|
+
return name === 'call_tool' && call?.arguments?.name === REACTION_TOOL_NAME;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The ids of a batch's react calls. */
|
|
73
|
+
export function reactionCallIds(calls) {
|
|
74
|
+
return new Set(
|
|
75
|
+
(calls || [])
|
|
76
|
+
.filter(isReactionToolCall)
|
|
77
|
+
.map((call) => call?.id)
|
|
78
|
+
.filter(Boolean)
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// What each successful react call actually hit, keyed by session and tool
|
|
83
|
+
// call id: the tool result reaches the loop as plain text (internal-tools.mjs
|
|
84
|
+
// normalizes every handler return to a string), so this is the result's
|
|
85
|
+
// metadata side channel. `{ messageId, room }`: the id of the message
|
|
86
|
+
// reactToMessage reacted on, and whether that log is a group room's (a
|
|
87
|
+
// separate transcript from the worker's own).
|
|
88
|
+
//
|
|
89
|
+
// A record belongs to the loop of its session: the batch that ran the call
|
|
90
|
+
// consumes it (takeReactionTarget), agentLoop clears the session's leftovers
|
|
91
|
+
// whenever it ends, success or not (clearReactionTargets), and the cap bounds
|
|
92
|
+
// the map even if neither ever ran.
|
|
93
|
+
const REACTION_TARGET_CAP = 256;
|
|
94
|
+
const reactionTargets = new Map();
|
|
95
|
+
const targetKey = (sessionId, toolCallId) => `${sessionId}\u0000${toolCallId}`;
|
|
96
|
+
|
|
97
|
+
/** Records the message a successful react call landed on. */
|
|
98
|
+
export function noteReactionTarget(sessionId, toolCallId, target) {
|
|
99
|
+
if (!sessionId || !toolCallId || !target?.messageId) return;
|
|
100
|
+
reactionTargets.set(targetKey(sessionId, toolCallId), target);
|
|
101
|
+
if (reactionTargets.size > REACTION_TARGET_CAP) reactionTargets.delete(reactionTargets.keys().next().value);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Drops every record of `sessionId`: its loop has ended. */
|
|
105
|
+
export function clearReactionTargets(sessionId) {
|
|
106
|
+
const prefix = `${sessionId}\u0000`;
|
|
107
|
+
for (const key of reactionTargets.keys()) if (key.startsWith(prefix)) reactionTargets.delete(key);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** How many records are held (tests). */
|
|
111
|
+
export function reactionTargetCount() {
|
|
112
|
+
return reactionTargets.size;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** The recorded target of a react call, consumed; null when it recorded none. */
|
|
116
|
+
export function takeReactionTarget(sessionId, toolCallId) {
|
|
117
|
+
const key = targetKey(sessionId, toolCallId);
|
|
118
|
+
const target = reactionTargets.get(key) ?? null;
|
|
119
|
+
reactionTargets.delete(key);
|
|
120
|
+
return target;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Whether a tool result reports one of `ids` (reactionCallIds) as executed
|
|
124
|
+
* and succeeded: a rejected, denied, skipped or failed call is not a reaction,
|
|
125
|
+
* and neither is an early UI-only notify that precedes the real result. */
|
|
126
|
+
export function isReactionSuccess(message, ids) {
|
|
127
|
+
return (
|
|
128
|
+
message?.role === 'tool' &&
|
|
129
|
+
message.__earlyNotify !== true &&
|
|
130
|
+
message.toolKind === 'normal' &&
|
|
131
|
+
ids.has(message.toolCallId)
|
|
132
|
+
);
|
|
133
|
+
}
|
|
@@ -77,6 +77,9 @@ export const DEFERRED_DEFAULT_LEAD_TOOLS = Object.freeze([
|
|
|
77
77
|
// Always on the Lead surface (native and bridge alike): a question is an
|
|
78
78
|
// optional move the model skips when it costs a load or call_tool hop.
|
|
79
79
|
'ask_user',
|
|
80
|
+
// A bot's reaction, likewise: only a bot's chat with the user has it at all
|
|
81
|
+
// (session-tool-surface.mjs reactionDisabled).
|
|
82
|
+
'react',
|
|
80
83
|
'Skill',
|
|
81
84
|
'load_tool',
|
|
82
85
|
]);
|
|
@@ -73,7 +73,8 @@ export const SESSION_TOOL = {
|
|
|
73
73
|
openWorldHint: false,
|
|
74
74
|
agentHidden: true,
|
|
75
75
|
},
|
|
76
|
-
trigger:
|
|
76
|
+
trigger:
|
|
77
|
+
'Session lookup/control, bot status, agent task sessions and their initial location, title, compact, fork, Project.',
|
|
77
78
|
description:
|
|
78
79
|
'Inspect sessions or control another by exact ID, never a guessed one. send acknowledges queue intake, not completion: promise a report of it only when its result has reportsBack: true. create returns status: started — started, not done yet; it reports its final answer, after the agents it started finish, unless its result says reportsBack: false. reportsBack: false carries reportsBackReason, the reason to tell the user. Cancel/delete require the user’s request. Other actions affect only the current session. A new session’s initial location (Project folder branch or its own worktree) is set here, with create’s worktree; worktree operations on existing sessions (enter, return, merge, remove, list, prune) use the git tool’s action:"worktree". Pass files on with attachments; a reported turn returns the files it produced as attachments.',
|
|
79
80
|
inputSchema: {
|
|
@@ -97,11 +98,20 @@ export const SESSION_TOOL = {
|
|
|
97
98
|
'list-projects',
|
|
98
99
|
'favorite',
|
|
99
100
|
'archive',
|
|
101
|
+
'suggest',
|
|
100
102
|
],
|
|
101
103
|
description:
|
|
102
|
-
'status defaults to current; list pages sessions, without sub-agent or archived ones unless include_agents/include_archived; bots shows each bot’s activity, work, approvals, routines and attention flags, or with session_id (a bot id or its chat) its routines and recent sessions; read returns latest items; send queues prompt (reportsBack: the turn result reports back here); cancel stops the turn; delete removes the session. create (bot conversations) starts a visible agent task session from brief (started, not done yet); unknown Project: list-projects first, still ambiguous: ask the user. compact runs after this turn; fork copies it without switching.',
|
|
104
|
+
'status defaults to current; list pages sessions, without sub-agent or archived ones unless include_agents/include_archived; bots shows each bot’s activity, work, approvals, routines and attention flags, or with session_id (a bot id or its chat) its routines and recent sessions; read returns latest items; send queues prompt (reportsBack: the turn result reports back here); cancel stops the turn; delete removes the session. create (bot conversations) starts a visible agent task session from brief (started, not done yet); unknown Project: list-projects first, still ambiguous: ask the user. compact runs after this turn; fork copies it without switching. suggest (Mixy’s chat only) records a proposal made to the user under key; value false records that the user declined it.',
|
|
105
|
+
},
|
|
106
|
+
title: {
|
|
107
|
+
type: 'string',
|
|
108
|
+
description: 'rename: new current-session title; create: task title; suggest: the proposal in one line.',
|
|
109
|
+
},
|
|
110
|
+
key: {
|
|
111
|
+
type: 'string',
|
|
112
|
+
description:
|
|
113
|
+
'suggest: stable action:target key, e.g. pause-routine:daily-news; the same key refreshes its proposal.',
|
|
103
114
|
},
|
|
104
|
-
title: { type: 'string', description: 'rename: new current-session title; create: task title.' },
|
|
105
115
|
session_id: {
|
|
106
116
|
type: 'string',
|
|
107
117
|
description:
|
|
@@ -150,7 +160,7 @@ export const SESSION_TOOL = {
|
|
|
150
160
|
'Existing directory; implies set-project. Resolve names with list-projects first. Shell cd does not switch Project.',
|
|
151
161
|
},
|
|
152
162
|
compact: { type: 'boolean', description: 'fork: compact the copy.' },
|
|
153
|
-
value: { type: 'boolean', description: 'favorite/archive: on or off.' },
|
|
163
|
+
value: { type: 'boolean', description: 'favorite/archive: on or off; suggest: false = the user declined it.' },
|
|
154
164
|
brief: { type: 'string', description: 'create: self-contained instruction, the first message.' },
|
|
155
165
|
project: { type: 'string', description: 'create: registered Project name or existing directory.' },
|
|
156
166
|
worktree: {
|
|
@@ -171,7 +181,13 @@ export const SESSION_TOOL = {
|
|
|
171
181
|
},
|
|
172
182
|
model: {
|
|
173
183
|
type: 'string',
|
|
174
|
-
description:
|
|
184
|
+
description:
|
|
185
|
+
'create: provider/model, preset, or a model menu display name (e.g. "Claude Opus 5.5", case-insensitive; a trailing effort word is read as effort); default is the Lead model (else the last-used one).',
|
|
186
|
+
},
|
|
187
|
+
effort: {
|
|
188
|
+
type: 'string',
|
|
189
|
+
description:
|
|
190
|
+
'create/send: reasoning effort setting of the session (auto, or a level its model supports, e.g. low, medium, high, xhigh, max); wins over one in model. send: bots may change only task sessions they created. status shows effort and fast.',
|
|
175
191
|
},
|
|
176
192
|
notify: { type: 'boolean', description: 'create: report results back, default true.' },
|
|
177
193
|
attachments: {
|
|
@@ -248,6 +264,7 @@ export function collectStandaloneToolDefs({
|
|
|
248
264
|
visualizeToolDefs = [],
|
|
249
265
|
setupToolDefs = [],
|
|
250
266
|
askUserToolDefs = [],
|
|
267
|
+
reactionToolDefs = [],
|
|
251
268
|
goalTools = [],
|
|
252
269
|
agentTools = [],
|
|
253
270
|
skillToolEnabled = true,
|
|
@@ -269,6 +286,7 @@ export function collectStandaloneToolDefs({
|
|
|
269
286
|
...admitted(visualizeToolDefs, VISUALIZE_RUNTIME_TOOLS),
|
|
270
287
|
...setupToolDefs,
|
|
271
288
|
...askUserToolDefs,
|
|
289
|
+
...reactionToolDefs,
|
|
272
290
|
...goalTools,
|
|
273
291
|
...agentTools,
|
|
274
292
|
];
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
import { HEARTBEAT_SOURCE } from '../../../shared/routine-trigger.mjs';
|
|
2
|
+
|
|
1
3
|
const INTERACTIVE_TOOL_PROFILE = 'interactive';
|
|
2
4
|
export const HEADLESS_TOOL_PROFILE = 'headless';
|
|
5
|
+
// Mixy's hidden heartbeat session (session-runtime/mixy-heartbeat.mjs): it
|
|
6
|
+
// judges a reviewed digest, so it reads sessions and bots, records proposals
|
|
7
|
+
// (session) and pauses or reruns a routine (setup); nothing else.
|
|
8
|
+
export const HEARTBEAT_TOOL_PROFILE = 'heartbeat';
|
|
3
9
|
|
|
4
10
|
export const HEADLESS_MODEL_TOOL_NAMES = Object.freeze([
|
|
5
11
|
'find',
|
|
@@ -21,23 +27,42 @@ export const HEADLESS_MODEL_TOOL_NAMES = Object.freeze([
|
|
|
21
27
|
'tidy',
|
|
22
28
|
]);
|
|
23
29
|
|
|
24
|
-
|
|
30
|
+
// load_tool loads the two deferred tools' schemas.
|
|
31
|
+
export const HEARTBEAT_MODEL_TOOL_NAMES = Object.freeze(['session', 'setup', 'load_tool']);
|
|
32
|
+
|
|
33
|
+
// The model tools each narrowed profile allows; the interactive profile has no list.
|
|
34
|
+
const PROFILE_TOOL_NAMES = new Map([
|
|
35
|
+
[HEADLESS_TOOL_PROFILE, HEADLESS_MODEL_TOOL_NAMES],
|
|
36
|
+
[HEARTBEAT_TOOL_PROFILE, HEARTBEAT_MODEL_TOOL_NAMES],
|
|
37
|
+
]);
|
|
38
|
+
const PROFILE_TOOL_SETS = new Map(
|
|
39
|
+
[...PROFILE_TOOL_NAMES].map(([profile, names]) => [profile, new Set(names.map((name) => name.toLowerCase()))])
|
|
40
|
+
);
|
|
25
41
|
|
|
26
42
|
export function normalizeToolProfile(value) {
|
|
27
|
-
|
|
43
|
+
const profile = String(value || '')
|
|
28
44
|
.trim()
|
|
29
|
-
.toLowerCase()
|
|
30
|
-
|
|
31
|
-
|
|
45
|
+
.toLowerCase();
|
|
46
|
+
return PROFILE_TOOL_NAMES.has(profile) ? profile : INTERACTIVE_TOOL_PROFILE;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The profile a session runtime runs under: a session created as a heartbeat
|
|
50
|
+
* run (source `heartbeat`) is fenced to the heartbeat tools whatever profile
|
|
51
|
+
* was asked for. The source only ever narrows, so a forged one costs nothing. */
|
|
52
|
+
export function sessionToolProfile({ toolProfile, sessionProfile } = {}) {
|
|
53
|
+
if (String(sessionProfile?.sourceName || '').trim() === HEARTBEAT_SOURCE) return HEARTBEAT_TOOL_PROFILE;
|
|
54
|
+
return normalizeToolProfile(toolProfile);
|
|
32
55
|
}
|
|
33
56
|
|
|
34
57
|
export function modelToolSchemaAllowlist(profile) {
|
|
35
|
-
|
|
58
|
+
const names = PROFILE_TOOL_NAMES.get(normalizeToolProfile(profile));
|
|
59
|
+
return names ? [...names] : null;
|
|
36
60
|
}
|
|
37
61
|
|
|
38
62
|
export function modelToolAllowedForProfile(name, profile) {
|
|
39
|
-
|
|
40
|
-
return
|
|
63
|
+
const allowed = PROFILE_TOOL_SETS.get(normalizeToolProfile(profile));
|
|
64
|
+
if (!allowed) return true;
|
|
65
|
+
return allowed.has(
|
|
41
66
|
String(name || '')
|
|
42
67
|
.trim()
|
|
43
68
|
.toLowerCase()
|
|
@@ -46,12 +71,12 @@ export function modelToolAllowedForProfile(name, profile) {
|
|
|
46
71
|
|
|
47
72
|
export function filterModelToolsForProfile(tools, profile) {
|
|
48
73
|
const rows = Array.isArray(tools) ? tools : [];
|
|
49
|
-
if (normalizeToolProfile(profile)
|
|
74
|
+
if (!PROFILE_TOOL_SETS.has(normalizeToolProfile(profile))) return rows;
|
|
50
75
|
return rows.filter((tool) => modelToolAllowedForProfile(tool?.name, profile));
|
|
51
76
|
}
|
|
52
77
|
|
|
53
78
|
export function disallowedModelToolNamesForProfile(tools, profile) {
|
|
54
|
-
if (normalizeToolProfile(profile)
|
|
79
|
+
if (!PROFILE_TOOL_SETS.has(normalizeToolProfile(profile))) return [];
|
|
55
80
|
return [
|
|
56
81
|
...new Set(
|
|
57
82
|
(Array.isArray(tools) ? tools : [])
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
// shipped starter after the user deletes it, so package updates do not
|
|
4
4
|
// silently resurrect the role.
|
|
5
5
|
import { join } from 'node:path';
|
|
6
|
-
import { existsSync } from 'node:fs';
|
|
6
|
+
import { existsSync, statSync } from 'node:fs';
|
|
7
7
|
import { clean } from '../session-text.mjs';
|
|
8
|
-
import {
|
|
8
|
+
import { parseJsonText } from '../../../../shared/atomic-file.mjs';
|
|
9
|
+
import { defaultDirResolver, readTextSafe } from '../fs-utils.mjs';
|
|
9
10
|
import { SHARED_SCAN_REVALIDATE_MS, createSharedScanCache } from './shared-scan-cache.mjs';
|
|
10
11
|
import { isHiddenAgent } from '../../internal-agents.mjs';
|
|
11
12
|
import { isAgentDisabled } from '../../../../shared/agent-route-config.mjs';
|
|
@@ -30,6 +31,27 @@ import {
|
|
|
30
31
|
setAgentDefinitionCache,
|
|
31
32
|
} from '../workflow-ids.mjs';
|
|
32
33
|
|
|
34
|
+
/** The entry file name an agent folder's manifest names (default AGENT.md). */
|
|
35
|
+
export const agentEntryFile = (manifest) => clean(manifest?.entry) || 'AGENT.md';
|
|
36
|
+
|
|
37
|
+
/** The entry file path of the agent folder `dir` for `manifest`, exactly as the
|
|
38
|
+
* loader reads it. The one resolver the loader, discovery, presence and the
|
|
39
|
+
* editor's revision share. */
|
|
40
|
+
export const agentEntryPath = (dir, manifest) => join(dir, agentEntryFile(manifest));
|
|
41
|
+
|
|
42
|
+
/** The agent.json object in `text` (a leading BOM tolerated), or null when it
|
|
43
|
+
* is not a JSON object. The one manifest parser: loader, discovery, editor. */
|
|
44
|
+
export function parseManifestText(text) {
|
|
45
|
+
try {
|
|
46
|
+
const value = parseJsonText(String(text));
|
|
47
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const manifestOfText = (text) => parseManifestText(text) || {};
|
|
54
|
+
|
|
33
55
|
// Yields its fs requests; see shared-scan-cache.mjs. With the bot store
|
|
34
56
|
// active, bots/ is scanned too and the agents/ originals of the ids the split
|
|
35
57
|
// copied are skipped (agent-store.mjs).
|
|
@@ -69,6 +91,21 @@ function* scanCustomAgentIds(userRoot, shippedRoot, botsRoot = null, migrated =
|
|
|
69
91
|
export const AGENT_LIST_REVALIDATE_MS = SHARED_SCAN_REVALIDATE_MS;
|
|
70
92
|
const agentListCache = createSharedScanCache();
|
|
71
93
|
|
|
94
|
+
// mtime+size of each file ("-" when absent). loadAgentDefinition runs per
|
|
95
|
+
// agent on every status pulse and prompt build; a handful of stats is cheap.
|
|
96
|
+
function fileSignature(files) {
|
|
97
|
+
return files
|
|
98
|
+
.map((file) => {
|
|
99
|
+
try {
|
|
100
|
+
const st = statSync(file);
|
|
101
|
+
return `${st.mtimeMs}:${st.size}`;
|
|
102
|
+
} catch {
|
|
103
|
+
return '-';
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
.join('|');
|
|
107
|
+
}
|
|
108
|
+
|
|
72
109
|
export function createWorkflowAgents({ rootDir, dataDir, readMarkdownDocument, normalizeAgentPermissionOrNone }) {
|
|
73
110
|
const defaultDir = defaultDirResolver(dataDir);
|
|
74
111
|
function agentSourceDirs(dir, id) {
|
|
@@ -111,12 +148,24 @@ export function createWorkflowAgents({ rootDir, dataDir, readMarkdownDocument, n
|
|
|
111
148
|
const agentId = normalizeAgentId(id) || normalizeWorkflowId(id);
|
|
112
149
|
if (!agentId) return null;
|
|
113
150
|
const cacheKey = `${dir || defaultDir()}\n${agentId}`;
|
|
114
|
-
|
|
151
|
+
const cached = agentDefinitionCache.get(cacheKey);
|
|
152
|
+
// A hit is trusted only while the files it was built from are unchanged,
|
|
153
|
+
// so a direct edit of AGENT.md/agent.json/PERSONA.md shows without a restart.
|
|
154
|
+
if (cached && fileSignature(cached.files) === cached.signature) return cached.definition;
|
|
115
155
|
const slot = FIXED_AGENT_SLOTS.find((agent) => agent.id === agentId);
|
|
156
|
+
const files = [];
|
|
157
|
+
const remember = (definition) => {
|
|
158
|
+
setAgentDefinitionCache(cacheKey, { definition, files, signature: fileSignature(files) });
|
|
159
|
+
return definition;
|
|
160
|
+
};
|
|
116
161
|
for (const d of agentSourceDirs(dir, agentId)) {
|
|
117
|
-
const manifest =
|
|
118
|
-
const entry =
|
|
119
|
-
|
|
162
|
+
const manifest = manifestOfText(readTextSafe(join(d, 'agent.json')));
|
|
163
|
+
const entry = agentEntryPath(d, manifest);
|
|
164
|
+
// Shipped files change only with an install update (a restart).
|
|
165
|
+
if (d !== join(rootDir, 'agents', agentId)) {
|
|
166
|
+
files.push(join(d, 'agent.json'), entry, join(d, 'PERSONA.md'), join(d, AGENT_DELETED_MARKER));
|
|
167
|
+
}
|
|
168
|
+
const doc = readMarkdownDocument(readTextSafe(entry));
|
|
120
169
|
const body = doc.body;
|
|
121
170
|
if (!body) continue;
|
|
122
171
|
const definition = {
|
|
@@ -136,13 +185,11 @@ export function createWorkflowAgents({ rootDir, dataDir, readMarkdownDocument, n
|
|
|
136
185
|
// Optional PERSONA.md beside AGENT.md (same dir, same cache entry).
|
|
137
186
|
persona: (readTextSafe(join(d, 'PERSONA.md')) || '').trim(),
|
|
138
187
|
};
|
|
139
|
-
|
|
140
|
-
return definition;
|
|
188
|
+
return remember(definition);
|
|
141
189
|
}
|
|
142
190
|
// Every shipped and user role lives at agents/<id>/AGENT.md; there is no
|
|
143
191
|
// flat agents/<id>.md layout left to fall back to.
|
|
144
|
-
|
|
145
|
-
return null;
|
|
192
|
+
return remember(null);
|
|
146
193
|
}
|
|
147
194
|
|
|
148
195
|
return { listCustomAgentIds, delegatableAgentIds, loadAgentDefinition };
|
|
@@ -59,13 +59,13 @@ export function createWorkflowContext({ packs, agents }) {
|
|
|
59
59
|
// - coding: the orchestration mode block and the full agent catalog;
|
|
60
60
|
// - agent mode: the fixed delegation block (no per-agent setting) and the
|
|
61
61
|
// sub-agent catalog — identical for every agent, so their prompts share
|
|
62
|
-
// it — plus
|
|
63
|
-
function leadPromptParts(config, dir, { agentMode = false,
|
|
62
|
+
// it — plus the contact-agent catalog: every bot but `agentId` itself.
|
|
63
|
+
function leadPromptParts(config, dir, { agentMode = false, agentId = '' } = {}) {
|
|
64
64
|
const mode = configuredOrchestrationMode(config);
|
|
65
65
|
const ids = agents.delegatableAgentIds(config, dir);
|
|
66
66
|
const isContact = (id) => isContactAgentId(id, agents.loadAgentDefinition(dir, id)?.kind);
|
|
67
67
|
if (!agentMode) {
|
|
68
|
-
// Bots are never a coding Lead's agents: only
|
|
68
|
+
// Bots are never a coding Lead's agents: only agent-mode Leads hand them work.
|
|
69
69
|
const workflowIds = ids.filter((id) => !isContact(id));
|
|
70
70
|
const catalog = agentCatalog(dir, workflowIds);
|
|
71
71
|
return {
|
|
@@ -79,7 +79,10 @@ export function createWorkflowContext({ packs, agents }) {
|
|
|
79
79
|
dir,
|
|
80
80
|
ids.filter((id) => !isContact(id))
|
|
81
81
|
);
|
|
82
|
-
const contacts =
|
|
82
|
+
const contacts = agentCatalog(
|
|
83
|
+
dir,
|
|
84
|
+
ids.filter((id) => isContact(id) && id !== agentId)
|
|
85
|
+
);
|
|
83
86
|
return {
|
|
84
87
|
// An agent-mode Lead without agents has no one to delegate to at all.
|
|
85
88
|
orchestrationContext: subAgents || contacts ? AGENT_MODE_DELEGATION : '',
|
|
@@ -110,8 +113,8 @@ export function createWorkflowContext({ packs, agents }) {
|
|
|
110
113
|
// orchestration instructions / agent catalog.
|
|
111
114
|
packContext,
|
|
112
115
|
context: [packContext, orchestrationContextBlock(config, dir)].filter(Boolean).join('\n\n'),
|
|
113
|
-
// A Lead's BP3 slices beside the pack: `{ agentMode,
|
|
114
|
-
// coding,
|
|
116
|
+
// A Lead's BP3 slices beside the pack: `{ agentMode, agentId }` picks the
|
|
117
|
+
// coding variant, or the agent variant of the agent `agentId` (session/agent-mode.mjs).
|
|
115
118
|
leadPromptParts: (options) => leadPromptParts(config, dir, options),
|
|
116
119
|
};
|
|
117
120
|
}
|
|
@@ -15,7 +15,7 @@ export const BUILTIN_SLOT_AGENT_IDS = new Set(
|
|
|
15
15
|
FIXED_AGENT_SLOTS.filter((agent) => agent.workflowSlot).map((agent) => agent.id)
|
|
16
16
|
);
|
|
17
17
|
export const STARTER_AGENT_ORDER = new Map(DEFAULT_DISABLED_AGENT_IDS.map((id, index) => [id, index]));
|
|
18
|
-
// Only Mixy
|
|
18
|
+
// Only agent-mode Leads (Mixy and the bots) delegate to contacts (agent-route-config.mjs isContactAgentId).
|
|
19
19
|
export { isContactAgentId } from '../../../shared/agent-route-config.mjs';
|
|
20
20
|
export const DEFAULT_WORKFLOW_ID = 'default';
|
|
21
21
|
|
|
@@ -10,8 +10,10 @@ import {
|
|
|
10
10
|
import { repairTranscriptBeforeProviderSend } from './loop/transcript-repair.mjs';
|
|
11
11
|
import { resetAccountProbePacing } from '../providers/account-pool.mjs';
|
|
12
12
|
import { createLoopState, finishLoop } from './loop/loop-state.mjs';
|
|
13
|
+
import { clearReactionTargets } from '../runtime-core/reaction-tool-defs.mjs';
|
|
13
14
|
import { applyRetryAction, beginIteration, sendProviderRequest, settleSendResult } from './loop/send-phase.mjs';
|
|
14
15
|
import { runToolPhase } from './loop/tool-phase.mjs';
|
|
16
|
+
import { endedWithoutReply } from './loop/turn-limits.mjs';
|
|
15
17
|
|
|
16
18
|
// Facade re-exports: these symbols live in ./loop/ modules and are part of
|
|
17
19
|
// loop.mjs's public surface (imported by scripts/tests and other runtime
|
|
@@ -44,6 +46,16 @@ export {
|
|
|
44
46
|
* ./loop/tool-phase.mjs.
|
|
45
47
|
*/
|
|
46
48
|
export async function agentLoop(provider, messages, model, tools, onToolCall, cwd, sendOpts) {
|
|
49
|
+
try {
|
|
50
|
+
return await runAgentLoop(provider, messages, model, tools, onToolCall, cwd, sendOpts);
|
|
51
|
+
} finally {
|
|
52
|
+
// A react call's recorded target belongs to this loop: whatever the batch
|
|
53
|
+
// did not consume (cancelled or failed turn) ends with it.
|
|
54
|
+
if (sendOpts?.sessionId) clearReactionTargets(sendOpts.sessionId);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function runAgentLoop(provider, messages, model, tools, onToolCall, cwd, sendOpts) {
|
|
47
59
|
// An explicit request asks for a current answer. Let the account pool
|
|
48
60
|
// re-measure quota it recorded as exhausted instead of refusing from the old
|
|
49
61
|
// reading — once per loop entry, not per provider round inside the turn.
|
|
@@ -67,6 +79,12 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
67
79
|
break;
|
|
68
80
|
}
|
|
69
81
|
await runToolPhase(state, round, sent, onToolCall);
|
|
82
|
+
// A capped turn that kept calling tools after it was told to reply ends
|
|
83
|
+
// here with no reply (./loop/turn-limits.mjs).
|
|
84
|
+
if (state.turnLimitEnded) {
|
|
85
|
+
state.response = endedWithoutReply(state.response);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
70
88
|
}
|
|
71
89
|
return finishLoop(state);
|
|
72
90
|
}
|
|
@@ -84,15 +84,12 @@ export const ROOM_LIMITS: Readonly<
|
|
|
84
84
|
Record<'maxRounds' | 'maxMessages', Readonly<{ min: number; max: number; default: number }>>
|
|
85
85
|
>;
|
|
86
86
|
export const ROOM_INSTRUCTION_LIMIT: number;
|
|
87
|
-
export const ROOM_PASS_REPLY: string;
|
|
88
87
|
export const ROOM_ROW_SOURCE: string;
|
|
89
|
-
export function isRoomId(value: unknown): value is string;
|
|
90
88
|
export function roomSessionId(roomId: unknown): string;
|
|
91
89
|
export function roomIdOfSession(sessionId: unknown): string;
|
|
92
90
|
export function roomMemberSessionId(roomId: unknown, agentId: unknown): string;
|
|
93
91
|
export function roomMemberOfSession(sessionId: unknown): { roomId: string; agentId: string } | null;
|
|
94
92
|
export function isRoomThreadSessionId(sessionId: unknown): boolean;
|
|
95
|
-
export function clampRoomLimit(kind: 'maxRounds' | 'maxMessages', value: unknown): number;
|
|
96
93
|
export function normalizeRoomConfig(value?: Partial<RoomConfig> & Record<string, unknown>): RoomConfig;
|
|
97
94
|
export function parseRoomAsk(
|
|
98
95
|
text: unknown,
|
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
// session-runtime/handoff-attachments.mjs): stored references, handed as
|
|
12
12
|
// content to the members the row reaches, once each.
|
|
13
13
|
import { escapeHarnessTags } from '../../../shared/shell-output-summary.mjs';
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
normalizeMessageAttachments,
|
|
16
|
+
transcriptAttachmentFields,
|
|
17
|
+
transcriptReactionsField,
|
|
18
|
+
} from '../../../shared/transcript-metadata.mjs';
|
|
15
19
|
import { AGENT_MESSAGE_NOTE } from '../../../shared/agent-message-envelope.mjs';
|
|
16
20
|
import { agentHomeSessionId } from './agent-home.mjs';
|
|
17
21
|
|
|
@@ -29,7 +33,7 @@ export const ROOM_LIMITS = Object.freeze({
|
|
|
29
33
|
/** The longest supervisor note a member's turn carries. */
|
|
30
34
|
export const ROOM_INSTRUCTION_LIMIT = 200;
|
|
31
35
|
/** A member's reply that says it has nothing to add. */
|
|
32
|
-
|
|
36
|
+
const ROOM_PASS_REPLY = '(pass)';
|
|
33
37
|
/** `meta.source` of every row stored in a room's log. */
|
|
34
38
|
export const ROOM_ROW_SOURCE = 'agent-room';
|
|
35
39
|
/** First-time members see at most this many earlier rows. */
|
|
@@ -44,7 +48,7 @@ const PART_TYPES = new Set(['image', 'file', 'text']);
|
|
|
44
48
|
const ATTACHMENT_LIMIT = 64;
|
|
45
49
|
const SENDER_SESSION = /^[A-Za-z0-9_.:-]{1,200}$/;
|
|
46
50
|
|
|
47
|
-
|
|
51
|
+
function isRoomId(value) {
|
|
48
52
|
return typeof value === 'string' && ROOM_ID.test(value);
|
|
49
53
|
}
|
|
50
54
|
|
|
@@ -86,7 +90,7 @@ export function isRoomThreadSessionId(sessionId) {
|
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
/** `value` as a whole number inside the `kind` limit's bounds. */
|
|
89
|
-
|
|
93
|
+
function clampRoomLimit(kind, value) {
|
|
90
94
|
const bounds = ROOM_LIMITS[kind];
|
|
91
95
|
const number = Math.round(Number(value));
|
|
92
96
|
if (!Number.isFinite(number)) return bounds.default;
|
|
@@ -776,6 +780,8 @@ export function roomRowItem(message, id) {
|
|
|
776
780
|
// A user row's (or member speech's) files, in the fields a chat prompt's row draws them from.
|
|
777
781
|
...(room.kind === 'user' || room.kind === 'speech' ? transcriptAttachmentFields(message.meta.transcript) : {}),
|
|
778
782
|
...(room.kind === 'user' && Array.isArray(images) && images.length ? { images } : {}),
|
|
783
|
+
// The members' emoji reactions on a user row (message-reactions.mjs).
|
|
784
|
+
...(room.kind === 'user' ? transcriptReactionsField(message.meta.transcript) : {}),
|
|
779
785
|
...roomRowSender(room),
|
|
780
786
|
};
|
|
781
787
|
}
|
|
@@ -90,14 +90,24 @@ function splitFreshSource(messages) {
|
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
// A cut at `end` that would split a surrogate pair (an emoji) moves back one
|
|
94
|
+
// unit: a lone surrogate serializes as an escape providers reject as invalid
|
|
95
|
+
// JSON, failing every Compact of the session.
|
|
96
|
+
function codePointEnd(text, end) {
|
|
97
|
+
const code = text.charCodeAt(end - 1);
|
|
98
|
+
return end < text.length && code >= 0xd800 && code <= 0xdbff ? end - 1 : end;
|
|
99
|
+
}
|
|
100
|
+
|
|
93
101
|
function chunkConversationMessage(role, content, source = {}) {
|
|
94
102
|
const text = String(content || '');
|
|
95
103
|
const out = [];
|
|
96
|
-
for (let offset = 0; offset < text.length;
|
|
104
|
+
for (let offset = 0; offset < text.length; ) {
|
|
105
|
+
const end = codePointEnd(text, offset + HANDOFF_SOURCE_CHUNK_CHARS);
|
|
97
106
|
const next = {
|
|
98
107
|
role,
|
|
99
|
-
content: text.slice(offset,
|
|
108
|
+
content: text.slice(offset, end),
|
|
100
109
|
};
|
|
110
|
+
offset = end;
|
|
101
111
|
if (Object.hasOwn(source, 'ts')) next.ts = source.ts;
|
|
102
112
|
if (Object.hasOwn(source, 'timestamp')) next.timestamp = source.timestamp;
|
|
103
113
|
out.push(next);
|
|
@@ -113,7 +123,9 @@ const SUMMARY_TOOL_ARGS_CHARS = 200;
|
|
|
113
123
|
const SUMMARY_TOOL_OUTCOME_CHARS = 300;
|
|
114
124
|
|
|
115
125
|
function boundedSummaryText(text, limit) {
|
|
116
|
-
|
|
126
|
+
if (text.length <= limit) return text;
|
|
127
|
+
const end = codePointEnd(text, limit);
|
|
128
|
+
return `${text.slice(0, end)}… [${text.length - end} more chars]`;
|
|
117
129
|
}
|
|
118
130
|
|
|
119
131
|
function toolCallSummaryLine(call, result) {
|
|
@@ -11,6 +11,7 @@ import { createEagerEntry, runEagerEntry } from './eager-dispatch/entry.mjs';
|
|
|
11
11
|
import { executeTool } from './loop/tool-exec.mjs';
|
|
12
12
|
import { isParallelDispatchable } from './loop/tool-helpers.mjs';
|
|
13
13
|
import { resolveExecutionToolCall } from './loop/tool-call-resolution.mjs';
|
|
14
|
+
import { turnLimitsOf } from './loop/turn-limits.mjs';
|
|
14
15
|
|
|
15
16
|
export function createEagerDispatcher({
|
|
16
17
|
tools,
|
|
@@ -37,6 +38,9 @@ export function createEagerDispatcher({
|
|
|
37
38
|
repeatFailLimit,
|
|
38
39
|
});
|
|
39
40
|
const barriers = createEagerBarriers({ cwd });
|
|
41
|
+
// A capped turn runs its calls in order, so none past its cap starts early
|
|
42
|
+
// (loop/turn-limits.mjs).
|
|
43
|
+
const capped = turnLimitsOf(opts) !== null;
|
|
40
44
|
const execute = (call, entry) =>
|
|
41
45
|
executeToolFn(call.name, call.arguments, cwd, sessionId, sessionRef, {
|
|
42
46
|
toolCallId: call.id,
|
|
@@ -52,6 +56,7 @@ export function createEagerDispatcher({
|
|
|
52
56
|
const startEagerTool = (wireCall) => {
|
|
53
57
|
// Streamed call_tool requests are admitted, ordered and executed as their
|
|
54
58
|
// targets; a projection error is answered by the serial batch instead.
|
|
59
|
+
if (capped) return null;
|
|
55
60
|
const call = resolveExecutionToolCall(wireCall, sessionRef);
|
|
56
61
|
if (!call?.id || pending.has(call.id) || !isParallelDispatchable(call.name)) return null;
|
|
57
62
|
const admitted = admission.admit(call);
|