mixdog 1.3.2 → 1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -8
- package/scripts/verify-runtime-dependency-versions.mjs +158 -0
- package/src/agents/reviewer/AGENT.md +5 -7
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +4 -0
- package/src/defaults/skills/browser-use/SKILL.md +6 -3
- package/src/defaults/skills/local-provider/SKILL.md +33 -6
- package/src/rules/lead/LEAD.md +5 -8
- package/src/rules/shared/45-destructive.md +5 -1
- package/src/rules/shared/60-verification.md +6 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +38 -11
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +15 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +17 -4
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/registry.mjs +10 -0
- package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +13 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +5 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +10 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +40 -5
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +32 -11
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +13 -0
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +68 -12
- package/src/runtime/agent/orchestrator/session/compact/continuation.mjs +15 -6
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +31 -8
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +44 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +117 -19
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +147 -137
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +37 -74
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +0 -2
- package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +13 -7
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/empty-nudge.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +62 -13
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +22 -6
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +12 -20
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/call-prep.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/manager/agent-listing-notice.mjs +102 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +24 -15
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +32 -1
- package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +17 -1
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +36 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -9
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +7 -5
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +7 -3
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +10 -1
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +7 -4
- package/src/runtime/agent/orchestrator/session/runtime-user-context.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +5 -4
- package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +140 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +76 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +33 -1
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.d.mts +5 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +37 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +18 -7
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +127 -20
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +557 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +6 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +8 -2
- package/src/runtime/attachments/legacy-office.mjs +110 -0
- package/src/runtime/attachments/prompt-restore.mjs +70 -0
- package/src/runtime/attachments/store.mjs +60 -3
- package/src/runtime/effort-judge/judge-client.mjs +61 -14
- package/src/runtime/effort-judge/model-install.mjs +34 -2
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +14 -2
- package/src/runtime/office/portable/portable-soffice.mjs +132 -30
- package/src/runtime/shared/agent-avatar-sprites.mjs +23 -8
- package/src/runtime/shared/agent-avatar.mjs +3 -2
- package/src/runtime/shared/agent-room-host.d.mts +31 -0
- package/src/runtime/shared/agent-room-host.mjs +58 -0
- package/src/runtime/shared/bot-hops.mjs +16 -11
- package/src/runtime/shared/llm/usage-rollup.mjs +4 -383
- package/src/runtime/shared/redact-directories.mjs +18 -0
- package/src/runtime/shared/remove-directory.mjs +28 -0
- package/src/runtime/shared/session-sidebar-host.d.mts +11 -2
- package/src/runtime/shared/session-sidebar-host.mjs +16 -3
- package/src/runtime/shared/tool-execution-contract.mjs +4 -0
- package/src/runtime/shared/tool-work-units.mjs +12 -9
- package/src/runtime/shared/turn-snapshot.mjs +233 -122
- package/src/runtime/shared/user-data-guard.mjs +51 -19
- package/src/runtime/shared/worktrees/activity-gate.mjs +209 -227
- package/src/runtime/shared/worktrees/contract.mjs +11 -3
- package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
- package/src/runtime/shared/worktrees/manager.mjs +419 -52
- package/src/runtime/shared/worktrees/registry.mjs +3 -1
- package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
- package/src/runtime/tidy/process.mjs +41 -8
- package/src/runtime/tidy/resolve.mjs +10 -7
- package/src/session-runtime/agent-home-delivery.mjs +73 -91
- package/src/session-runtime/agent-profile-changes.mjs +16 -0
- package/src/session-runtime/agent-room-delivery.mjs +2 -63
- package/src/session-runtime/agent-task-sessions.mjs +194 -64
- package/src/session-runtime/agent-tool-routing.mjs +49 -4
- package/src/session-runtime/boot/apis.mjs +3 -0
- package/src/session-runtime/boot/facade.mjs +5 -6
- package/src/session-runtime/boot/tools.mjs +23 -1
- package/src/session-runtime/held-requests.mjs +20 -25
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +5 -22
- package/src/session-runtime/model-recency.mjs +1 -0
- package/src/session-runtime/provider-models/row-hydration.mjs +8 -1
- package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
- package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
- package/src/session-runtime/runtime-review-api.mjs +4 -1
- package/src/session-runtime/services/agent-tool/close-flow.mjs +14 -2
- package/src/session-runtime/services/agent-tool/execute.mjs +6 -1
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +44 -20
- package/src/session-runtime/services/agent-tool/render.mjs +3 -1
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +3 -0
- package/src/session-runtime/services/agent-tool/tool-def.mjs +1 -1
- package/src/session-runtime/services/provider-admin.mjs +12 -2
- package/src/session-runtime/services/worktree-access.mjs +27 -0
- package/src/session-runtime/session-hooks.mjs +9 -17
- package/src/session-runtime/settings-builtin-tools-api.mjs +2 -0
- package/src/session-runtime/setup-tool/executor.mjs +4 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +3 -0
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +98 -14
- package/src/session-runtime/tool-policy/policy-stage.mjs +23 -5
- package/src/session-runtime/tool-policy-refresh.mjs +1 -0
- package/src/session-runtime/tool-surface.mjs +9 -5
- package/src/session-runtime/turn/session-ops.mjs +8 -0
- package/src/session-runtime/turn/turn-run.mjs +5 -1
- package/src/session-runtime/usage-stats-api.mjs +1 -1
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +10 -2
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +39 -17
- package/src/session-runtime/workspace/transition-steps.mjs +1 -18
- package/src/session-runtime/workspace/transitions.mjs +279 -67
- package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
- package/src/standalone/daemon-lag-profiler.mjs +3 -1
- package/src/standalone/daemon-session-control.mjs +140 -23
- package/src/standalone/daemon-telemetry.mjs +9 -0
- package/src/standalone/daemon.mjs +5 -0
- package/src/standalone/local-session-runtime.mjs +11 -2
- package/src/standalone/session-protocol.mjs +1 -0
- package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
- package/src/standalone/session-service/agent-route-follow.mjs +69 -0
- package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
- package/src/standalone/session-service.mjs +10 -0
- package/src/standalone/session-tool-output.mjs +1 -0
- package/src/standalone/staged-import.mjs +47 -0
- package/src/tui/App.jsx +2 -0
- package/src/tui/app/message-selector.mjs +15 -2
- package/src/tui/app/model-options.mjs +16 -2
- package/src/tui/app/model-picker.mjs +2 -1
- package/src/tui/app/route-pickers.mjs +3 -1
- package/src/tui/dist/index.mjs +403 -210
- package/src/tui/paste-attachments.mjs +0 -1
- package/src/tui/paste-text-policy.mjs +4 -5
- package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
- package/src/tui/session/notice-surface.mjs +3 -0
- package/src/tui/session/session-api/intake/abort.mjs +33 -17
- package/src/tui/session/session-api/intake/rewind.mjs +39 -4
- package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
- package/src/tui/session/tool-approval.mjs +11 -48
- package/src/vendor/statusline/src/gateway/claude-current.mjs +0 -58
- package/src/vendor/statusline/src/gateway/route-meta.mjs +1 -361
- package/src/vendor/statusline/src/gateway/session-routes.mjs +0 -55
- package/src/session-runtime/room-member-gate.mjs +0 -61
- package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +0 -285
|
@@ -58,7 +58,15 @@ function strictPositiveInt(value, label) {
|
|
|
58
58
|
return value;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
const OVERRIDE_KEYS = new Set([
|
|
61
|
+
const OVERRIDE_KEYS = new Set([
|
|
62
|
+
'manual',
|
|
63
|
+
'hidden',
|
|
64
|
+
'name',
|
|
65
|
+
'contextWindow',
|
|
66
|
+
'maxOutputTokens',
|
|
67
|
+
'supportsVision',
|
|
68
|
+
'reasoning',
|
|
69
|
+
]);
|
|
62
70
|
const REASONING_KEYS = new Set(['levels', 'wireFormat', 'wireValues', 'replay']);
|
|
63
71
|
const DISCOVERY_KEYS = new Set(['newModelsHidden']);
|
|
64
72
|
// v2 config fields, plus the transport fields callers pass alongside them and
|
|
@@ -220,6 +228,9 @@ function normalizeOverride(id, raw, protocol, legacy) {
|
|
|
220
228
|
if (contextWindow) out.contextWindow = contextWindow;
|
|
221
229
|
const maxOutputTokens = positiveInt(raw.maxOutputTokens, `${label}.maxOutputTokens`);
|
|
222
230
|
if (maxOutputTokens) out.maxOutputTokens = maxOutputTokens;
|
|
231
|
+
// Tri-state image input: absent = auto (catalog / default), true / false = the user's word.
|
|
232
|
+
const supportsVision = optionalBoolean(raw.supportsVision, `${label}.supportsVision`);
|
|
233
|
+
if (supportsVision !== undefined) out.supportsVision = supportsVision;
|
|
223
234
|
if (raw.reasoning !== undefined) {
|
|
224
235
|
const reasoning = normalizeReasoning(raw.reasoning, `${label}.reasoning`, protocol);
|
|
225
236
|
if (Object.keys(reasoning).length) out.reasoning = reasoning;
|
|
@@ -420,6 +431,8 @@ function effectiveRow(base, override, { providerId, discovered, newModelsHidden
|
|
|
420
431
|
outputTokens: ov.maxOutputTokens ?? (Number(base.outputTokens) > 0 ? Number(base.outputTokens) : null),
|
|
421
432
|
hidden,
|
|
422
433
|
manual: ov.manual === true,
|
|
434
|
+
// An explicit override (false included) beats the catalog; unset keeps the base.
|
|
435
|
+
...(typeof ov.supportsVision === 'boolean' ? { supportsVision: ov.supportsVision } : {}),
|
|
423
436
|
supportsReasoning: levels ? levels.some((level) => level !== 'none') : base.supportsReasoning === true,
|
|
424
437
|
...reasoningLevelFields(levels),
|
|
425
438
|
customReasoning: { wireFormat: reasoning.wireFormat ?? 'standard', wireValues, replay },
|
|
@@ -3,7 +3,7 @@ import { createNpmVersionSource, maxSemver } from './npm-cli-version.mjs';
|
|
|
3
3
|
// Grok CLI client version for the cli-chat-proxy version gate (HTTP 426).
|
|
4
4
|
// Effective = env override, else max(floor, learned-from-426, live npm latest
|
|
5
5
|
// of the official @xai-official/grok CLI).
|
|
6
|
-
export const GROK_CLI_VERSION_FLOOR = '1.0.
|
|
6
|
+
export const GROK_CLI_VERSION_FLOOR = '1.0.67';
|
|
7
7
|
export const GROK_CLI_NPM_PACKAGE = '@xai-official/grok';
|
|
8
8
|
|
|
9
9
|
const live = createNpmVersionSource(GROK_CLI_NPM_PACKAGE, { persistKey: 'grok-cli' });
|
|
@@ -518,6 +518,16 @@ export function providerNativePdf(name) {
|
|
|
518
518
|
export function providerReadsImages(name) {
|
|
519
519
|
return providers.get(String(name || '').toLowerCase())?.takesImages !== false;
|
|
520
520
|
}
|
|
521
|
+
/**
|
|
522
|
+
* The user's image-input override for one model of a custom provider: true or
|
|
523
|
+
* false as set, undefined when unset (auto) or the provider is not a loaded
|
|
524
|
+
* custom one. A pure lookup; it never instantiates a provider.
|
|
525
|
+
*/
|
|
526
|
+
export function providerModelVisionOverride(name, model) {
|
|
527
|
+
const overrides = providers.get(String(name || '').toLowerCase())?.customConfig?.modelOverrides;
|
|
528
|
+
const value = overrides && Object.hasOwn(overrides, model) ? overrides[model]?.supportsVision : undefined;
|
|
529
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
530
|
+
}
|
|
521
531
|
/**
|
|
522
532
|
* Whether the named provider's adapter declares `consumesReadWindows`: it
|
|
523
533
|
* needs the structured clip report of read results. A pure lookup; a proxy
|
|
@@ -9,7 +9,7 @@ import { OpenAICompatProvider, OPENAI_COMPAT_PRESETS } from './openai-compat.mjs
|
|
|
9
9
|
import { originBoundFetch } from './lib/origin-bound-fetch.mjs';
|
|
10
10
|
import { withInclusiveUsage } from './lib/inclusive-usage.mjs';
|
|
11
11
|
import { getModelMetadataSync } from './model-catalog.mjs';
|
|
12
|
-
import { resolveWireRoute, sendOnRoute } from './wire-route.mjs';
|
|
12
|
+
import { resolveWireRoute, sendOnRoute, sendWireRoute } from './wire-route.mjs';
|
|
13
13
|
|
|
14
14
|
// A Files API file-ID image anywhere in the history (tool results nest content).
|
|
15
15
|
function hasFileIdImage(value, depth = 0) {
|
|
@@ -66,6 +66,10 @@ export class GatewayMessagesEngine extends AnthropicProvider {
|
|
|
66
66
|
_sendOnce(messages, model, tools, opts) {
|
|
67
67
|
return super._sendOnce(messages, model, tools, gatewayMessagesOpts(this.config, model, messages, opts));
|
|
68
68
|
}
|
|
69
|
+
// Same ceiling a send resolves, so a retry never asks above the gateway's limit.
|
|
70
|
+
escalatedMaxTokens(model, opts) {
|
|
71
|
+
return super.escalatedMaxTokens(model, gatewayMessagesOpts(this.config, model, [], opts));
|
|
72
|
+
}
|
|
69
73
|
_createClient(apiKey) {
|
|
70
74
|
const client = super._createClient(apiKey);
|
|
71
75
|
if (this.config.authScheme === 'bearer') {
|
|
@@ -138,6 +142,14 @@ export class WireGatewayProvider {
|
|
|
138
142
|
});
|
|
139
143
|
}
|
|
140
144
|
|
|
145
|
+
// A higher one-shot max_tokens exists only on the Messages wire; the
|
|
146
|
+
// route is the same one the send itself resolves. Other wires: null.
|
|
147
|
+
async escalatedMaxTokens(model, opts, tools) {
|
|
148
|
+
const useModel = model || this.openai.defaultModel;
|
|
149
|
+
const route = await sendWireRoute(this, useModel, tools, opts);
|
|
150
|
+
return route?.wire === 'messages' ? this.anthropic.escalatedMaxTokens(useModel, opts) : null;
|
|
151
|
+
}
|
|
152
|
+
|
|
141
153
|
reloadApiKey() {
|
|
142
154
|
this.openai.reloadApiKey();
|
|
143
155
|
this.anthropic.reloadApiKey();
|
|
@@ -65,6 +65,10 @@ export function createGoalRuntime({
|
|
|
65
65
|
// context still owes, from the rules and state already delivered into it.
|
|
66
66
|
// In memory on purpose: a restart is a fresh context that needs them again.
|
|
67
67
|
continuationTiers: new Map(),
|
|
68
|
+
// Session -> Goal id whose turn ended in a stop (failed, cancelled, usage
|
|
69
|
+
// limit) the record could not save. The record still reads active, so
|
|
70
|
+
// without this a full disk would start turn after turn into the same error.
|
|
71
|
+
unsavedStops: new Map(),
|
|
68
72
|
closed: false,
|
|
69
73
|
};
|
|
70
74
|
Object.assign(ctx, createGoalStore(ctx));
|
|
@@ -116,6 +120,7 @@ export function createGoalRuntime({
|
|
|
116
120
|
ctx.observedGoals.clear();
|
|
117
121
|
ctx.idleReviewTurns.clear();
|
|
118
122
|
ctx.continuationTiers.clear();
|
|
123
|
+
ctx.unsavedStops.clear();
|
|
119
124
|
listeners.clear();
|
|
120
125
|
// Accepted writes keep their ordering until they settle. Callers may
|
|
121
126
|
// await this barrier before releasing the session's backing resources.
|
|
@@ -114,6 +114,7 @@ export function createGoalTurnLifecycle(ctx) {
|
|
|
114
114
|
turnStartedAt,
|
|
115
115
|
idleReviewTurns,
|
|
116
116
|
continuationTiers,
|
|
117
|
+
unsavedStops,
|
|
117
118
|
withMutation,
|
|
118
119
|
readRecord,
|
|
119
120
|
commit,
|
|
@@ -163,7 +164,14 @@ export function createGoalTurnLifecycle(ctx) {
|
|
|
163
164
|
applyTurnOutcome(goal, detail, at);
|
|
164
165
|
}
|
|
165
166
|
goal.updatedAt = at;
|
|
166
|
-
|
|
167
|
+
let published;
|
|
168
|
+
try {
|
|
169
|
+
published = await commit(id, goal);
|
|
170
|
+
} catch (error) {
|
|
171
|
+
if (goal.status !== 'active') unsavedStops.set(id, goal.id);
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
unsavedStops.delete(id);
|
|
167
175
|
if (automaticTurnWithoutActivity(detail, published)) idleReviewTurns.set(id, idleReviewKey(published));
|
|
168
176
|
return published;
|
|
169
177
|
});
|
|
@@ -189,6 +197,7 @@ export function createGoalTurnLifecycle(ctx) {
|
|
|
189
197
|
if (goal?.status !== 'active') return { run: false, reason: goal?.status || 'missing', goal };
|
|
190
198
|
if (runningAgentWork(agentStatus)) return { run: false, reason: 'agent-running', goal };
|
|
191
199
|
const id = assertSessionId(sessionId);
|
|
200
|
+
if (unsavedStops.get(id) === goal.id) return { run: false, reason: 'unsaved-stop', goal };
|
|
192
201
|
const answered = idleReviewTurns.get(id) === idleReviewKey(goal);
|
|
193
202
|
if (settledDurationWait(goal)) {
|
|
194
203
|
// The model already answered this exact list with no new work, so the
|
|
@@ -622,6 +622,18 @@ function reconcileStateMatches(a, b) {
|
|
|
622
622
|
return true;
|
|
623
623
|
}
|
|
624
624
|
|
|
625
|
+
// Tools whose schema the session itself keeps exposed: the active surface and
|
|
626
|
+
// the callable/discovered state. Session fields, so a compaction keeps them;
|
|
627
|
+
// discoveries known only from message history (native tool references) are
|
|
628
|
+
// not here, since compaction can remove that history.
|
|
629
|
+
function persistentToolSurfaceNames(session) {
|
|
630
|
+
return new Set([
|
|
631
|
+
...(session.tools || []).map((tool) => clean(tool?.name)).filter(Boolean),
|
|
632
|
+
...parseToolSelection(session.deferredCallableTools),
|
|
633
|
+
...parseToolSelection(session.deferredDiscoveredTools),
|
|
634
|
+
]);
|
|
635
|
+
}
|
|
636
|
+
|
|
625
637
|
function reconcileDeferredMcpToolCatalogUncached(session, liveMcpTools) {
|
|
626
638
|
const live = filterDisallowedTools(Array.isArray(liveMcpTools) ? liveMcpTools : [], session.disallowedTools);
|
|
627
639
|
const hadSnapshot = Array.isArray(session.deferredMcpToolNames);
|
|
@@ -660,11 +672,7 @@ function reconcileDeferredMcpToolCatalogUncached(session, liveMcpTools) {
|
|
|
660
672
|
const knownBefore = isToolBridgeEnabled(session)
|
|
661
673
|
? new Map(deferredCatalogUnion(session).map((tool) => [clean(tool?.name), tool]))
|
|
662
674
|
: null;
|
|
663
|
-
const active =
|
|
664
|
-
...(session.tools || []).map((tool) => clean(tool?.name)).filter(Boolean),
|
|
665
|
-
...parseToolSelection(session.deferredCallableTools),
|
|
666
|
-
...parseToolSelection(session.deferredDiscoveredTools),
|
|
667
|
-
]);
|
|
675
|
+
const active = persistentToolSurfaceNames(session);
|
|
668
676
|
|
|
669
677
|
const liveMcpByName = new Map();
|
|
670
678
|
for (const tool of live) {
|
|
@@ -723,6 +731,33 @@ function reconcileDeferredMcpToolCatalogUncached(session, liveMcpTools) {
|
|
|
723
731
|
return [...added.map((entry) => entry.name), ...removed];
|
|
724
732
|
}
|
|
725
733
|
|
|
734
|
+
/**
|
|
735
|
+
* Compaction strips the delta reminder from older user turns and the boot
|
|
736
|
+
* manifest in the system prompt is not rebuilt, so a late MCP tool announced
|
|
737
|
+
* before a compaction would stay invisible. Re-arm it, exactly as compaction
|
|
738
|
+
* re-marks the Goal reminder (markPendingGoalReminder): every currently
|
|
739
|
+
* connected tool the boot manifest never listed and the session has not loaded
|
|
740
|
+
* or discovered rides the next real prompt as one more delta, acknowledged
|
|
741
|
+
* only once the provider accepts that turn.
|
|
742
|
+
* Returns the re-armed names, or null when there are none.
|
|
743
|
+
*/
|
|
744
|
+
export function rearmLateDeferredTools(session) {
|
|
745
|
+
if (!session || !Array.isArray(session.tools) || fixedSurfaceSession(session)) return null;
|
|
746
|
+
if (!Array.isArray(session.deferredMcpToolNames) || !Array.isArray(session.deferredAnnouncedTools)) return null;
|
|
747
|
+
// Listed in the boot manifest, or already loaded/discovered: still visible.
|
|
748
|
+
const listed = new Set([...session.deferredAnnouncedTools, ...persistentToolSurfaceNames(session)]);
|
|
749
|
+
const connected = new Set(session.deferredMcpToolNames);
|
|
750
|
+
const added = [];
|
|
751
|
+
for (const tool of Array.isArray(session.deferredLateToolCatalog) ? session.deferredLateToolCatalog : []) {
|
|
752
|
+
const name = clean(tool?.name);
|
|
753
|
+
if (!name || !connected.has(name) || listed.has(name) || !rosterPermits(session, tool)) continue;
|
|
754
|
+
added.push({ name, description: lateAnnouncementDescription(tool?.description) });
|
|
755
|
+
}
|
|
756
|
+
if (!added.length) return null;
|
|
757
|
+
mergePendingDeferredToolDelta(session, { added });
|
|
758
|
+
return added.map((entry) => entry.name);
|
|
759
|
+
}
|
|
760
|
+
|
|
726
761
|
export function scopedMcpToolsFor(session, config = null) {
|
|
727
762
|
return filterMcpToolsForSession(getMcpTools(session?.mcpScopeId), session?.cwd || null, config);
|
|
728
763
|
}
|
|
@@ -73,9 +73,9 @@ export const SESSION_TOOL = {
|
|
|
73
73
|
openWorldHint: false,
|
|
74
74
|
agentHidden: true,
|
|
75
75
|
},
|
|
76
|
-
trigger: 'Session lookup/control, bot status, agent task sessions, title, compact, fork, Project
|
|
76
|
+
trigger: 'Session lookup/control, bot status, agent task sessions and their initial location, title, compact, fork, Project.',
|
|
77
77
|
description:
|
|
78
|
-
'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
|
|
78
|
+
'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
79
|
inputSchema: {
|
|
80
80
|
type: 'object',
|
|
81
81
|
properties: {
|
|
@@ -97,13 +97,9 @@ export const SESSION_TOOL = {
|
|
|
97
97
|
'list-projects',
|
|
98
98
|
'favorite',
|
|
99
99
|
'archive',
|
|
100
|
-
'merge-worktree',
|
|
101
|
-
'remove-worktree',
|
|
102
|
-
'list-worktrees',
|
|
103
|
-
'prune-worktrees',
|
|
104
100
|
],
|
|
105
101
|
description:
|
|
106
|
-
'status defaults to current; list pages sessions; 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.
|
|
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.',
|
|
107
103
|
},
|
|
108
104
|
title: { type: 'string', description: 'rename: new current-session title; create: task title.' },
|
|
109
105
|
session_id: {
|
|
@@ -112,6 +108,11 @@ export const SESSION_TOOL = {
|
|
|
112
108
|
'ID from list; required for read/send/cancel/delete, optional for status/bots. send/cancel/delete cannot target the caller.',
|
|
113
109
|
},
|
|
114
110
|
prompt: { type: 'string', description: 'send: message to enqueue; loads a cold session.' },
|
|
111
|
+
to: {
|
|
112
|
+
type: 'string',
|
|
113
|
+
description:
|
|
114
|
+
'send to a group room (sess_room_*): the member bot it is for, by agent id or room name (or start prompt with @name); only that member answers. A member session (sess_rmem_*) is never a send target.',
|
|
115
|
+
},
|
|
115
116
|
limit: {
|
|
116
117
|
type: 'integer',
|
|
117
118
|
minimum: 1,
|
|
@@ -120,6 +121,14 @@ export const SESSION_TOOL = {
|
|
|
120
121
|
'list/read: maximum rows, default 25; bots: sessions, default 10. Output: 8KiB per page, 12KiB for initial read tail; follow nextOffset/nextTextOffset for more.',
|
|
121
122
|
},
|
|
122
123
|
cursor: { type: 'string', description: 'list: returned nextCursor.' },
|
|
124
|
+
include_agents: {
|
|
125
|
+
type: 'boolean',
|
|
126
|
+
description: 'list: also sub-agent sessions, default false; your own agents: agent list/status.',
|
|
127
|
+
},
|
|
128
|
+
include_archived: {
|
|
129
|
+
type: 'boolean',
|
|
130
|
+
description: 'list: also archived sessions (archived: true), default false.',
|
|
131
|
+
},
|
|
123
132
|
offset: {
|
|
124
133
|
type: 'integer',
|
|
125
134
|
minimum: 0,
|
|
@@ -141,13 +150,25 @@ export const SESSION_TOOL = {
|
|
|
141
150
|
'Existing directory; implies set-project. Resolve names with list-projects first. Shell cd does not switch Project.',
|
|
142
151
|
},
|
|
143
152
|
compact: { type: 'boolean', description: 'fork: compact the copy.' },
|
|
144
|
-
force: {
|
|
145
|
-
type: 'boolean',
|
|
146
|
-
description: 'remove-worktree: allow uncommitted/unmerged work only when explicitly requested.',
|
|
147
|
-
},
|
|
148
153
|
value: { type: 'boolean', description: 'favorite/archive: on or off.' },
|
|
149
154
|
brief: { type: 'string', description: 'create: self-contained instruction, the first message.' },
|
|
150
155
|
project: { type: 'string', description: 'create: registered Project name or existing directory.' },
|
|
156
|
+
worktree: {
|
|
157
|
+
type: 'object',
|
|
158
|
+
description:
|
|
159
|
+
'create: where the new session starts, never carrying uncommitted changes. {mode:"branch"}: the Project folder on its current branch. {mode:"worktree", name?}: its own new managed worktree (name: letters, digits, ".", "_", "-"; default from the title); a non-git folder or unregistered Project is an error. Omitted: the user’s New task default. The brief is sent only once the session is placed.',
|
|
160
|
+
properties: {
|
|
161
|
+
mode: { type: 'string', enum: ['branch', 'worktree'] },
|
|
162
|
+
name: { type: 'string', maxLength: 64, description: 'mode worktree: worktree name.' },
|
|
163
|
+
},
|
|
164
|
+
required: ['mode'],
|
|
165
|
+
additionalProperties: false,
|
|
166
|
+
},
|
|
167
|
+
confirm_large: {
|
|
168
|
+
type: 'boolean',
|
|
169
|
+
description:
|
|
170
|
+
'create: only after the user confirmed the large-repository warning (WORKTREE_LARGE_REPO_CONFIRM, with its size) for the worktree.',
|
|
171
|
+
},
|
|
151
172
|
model: {
|
|
152
173
|
type: 'string',
|
|
153
174
|
description: 'create: provider/model or preset; default is the Lead model (else the last-used one).',
|
|
@@ -64,10 +64,13 @@ export function toSessionWorkflowMeta(workflow) {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
// Older sessions stored delegation policy here. Current summaries retain the
|
|
67
|
-
// effective capability
|
|
68
|
-
|
|
67
|
+
// effective capability: an empty agent catalog removes the tool from an
|
|
68
|
+
// agent-mode Lead only. A coding Lead keeps `agent` whichever agents exist;
|
|
69
|
+
// the agents it may delegate to reach it as a listing reminder
|
|
70
|
+
// (manager/agent-listing-notice.mjs) and a spawn of one switched off is refused.
|
|
71
|
+
export function workflowDisallowsAgentTool(workflow, { agentMode = false } = {}) {
|
|
69
72
|
if (!workflow || typeof workflow !== 'object') return false;
|
|
70
|
-
if (workflow.delegatesAgents === false) return true;
|
|
73
|
+
if (agentMode && workflow.delegatesAgents === false) return true;
|
|
71
74
|
return Boolean(workflow.agentsConfigured === true && Array.isArray(workflow.agents) && workflow.agents.length === 0);
|
|
72
75
|
}
|
|
73
76
|
|
|
@@ -61,7 +61,7 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
61
61
|
// owns a bounded recovery ladder in ./loop/no-tool-turn.mjs, which either
|
|
62
62
|
// appended a recovery turn ('continue') or produced the terminal response.
|
|
63
63
|
if (!state.response.toolCalls?.length) {
|
|
64
|
-
const outcome = state.noToolTurn.resolve(state.response, state.iterations);
|
|
64
|
+
const outcome = await state.noToolTurn.resolve(state.response, state.iterations);
|
|
65
65
|
state.response = outcome.response;
|
|
66
66
|
if (outcome.action === 'continue') continue;
|
|
67
67
|
break;
|
|
@@ -9,6 +9,11 @@ export interface RoomRow {
|
|
|
9
9
|
to?: string[] | string;
|
|
10
10
|
toName?: string[] | string;
|
|
11
11
|
failed?: boolean;
|
|
12
|
+
/** A user-kind row another session sent: its session, the sending bot
|
|
13
|
+
* (none for the user's Lead) and the name it is shown under. */
|
|
14
|
+
fromSession?: string;
|
|
15
|
+
fromAgent?: string;
|
|
16
|
+
fromName?: string;
|
|
12
17
|
limit?: 'rounds' | 'messages';
|
|
13
18
|
rounds?: number;
|
|
14
19
|
messages?: number;
|
|
@@ -93,6 +98,7 @@ export function parseRoomAsk(
|
|
|
93
98
|
text: unknown,
|
|
94
99
|
members?: readonly RoomMemberLabel[]
|
|
95
100
|
): { to: string[]; text: string; error?: 'member' | 'message' };
|
|
101
|
+
export function roomMemberNamed(value: unknown, members?: readonly RoomMemberLabel[]): string;
|
|
96
102
|
export function roomMentionOf(text: unknown, members?: readonly RoomMemberLabel[], selfId?: string): string;
|
|
97
103
|
export function roomRowTargets(row?: Partial<RoomRow>): RoomMemberLabel[];
|
|
98
104
|
export function isPassReply(text: unknown): boolean;
|
|
@@ -139,6 +145,8 @@ export function runRoomExchange(options: {
|
|
|
139
145
|
/** The user's newest message has no answer yet (default true): until a
|
|
140
146
|
* member speaks, a step must pick. */
|
|
141
147
|
waiting?: boolean;
|
|
148
|
+
/** The called members (`to`) answer and nobody else follows. */
|
|
149
|
+
only?: boolean;
|
|
142
150
|
}): Promise<RoomExchangeResult>;
|
|
143
151
|
export function roomDataLabel(value: unknown): string;
|
|
144
152
|
export function roomDataLines(text: unknown): string[];
|
|
@@ -165,6 +173,10 @@ export function roomTurnPrompt(options: {
|
|
|
165
173
|
scene?: RoomScene;
|
|
166
174
|
/** The user's newest message still has no answer (default: it is the newest row). */
|
|
167
175
|
waiting?: boolean;
|
|
176
|
+
/** The sender of the message this member was asked from another session. */
|
|
177
|
+
askedBy?: string;
|
|
178
|
+
/** The member's own reported work it relays to the room this turn. */
|
|
179
|
+
relay?: string;
|
|
168
180
|
}): string | RoomPromptPart[];
|
|
169
181
|
export function normalizeRoomRow(row?: Partial<RoomRow> & Record<string, unknown>): RoomRow;
|
|
170
182
|
export function roomRowMessage(row: Partial<RoomRow>): {
|
|
@@ -179,3 +191,4 @@ export function roomRowMessage(row: Partial<RoomRow>): {
|
|
|
179
191
|
export function isRoomRowMessage(message: unknown): boolean;
|
|
180
192
|
export function roomRowItem(message: unknown, id?: unknown): Record<string, unknown>;
|
|
181
193
|
export function roomRowsOfItems(items: unknown): RoomRow[];
|
|
194
|
+
export function roomRowSender(room: unknown): { fromAgent?: string; sender?: 'lead' };
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// content to the members the row reaches, once each.
|
|
13
13
|
import { escapeHarnessTags } from '../../../shared/shell-output-summary.mjs';
|
|
14
14
|
import { normalizeMessageAttachments, transcriptAttachmentFields } from '../../../shared/transcript-metadata.mjs';
|
|
15
|
+
import { AGENT_MESSAGE_NOTE } from '../../../shared/agent-message-envelope.mjs';
|
|
15
16
|
import { agentHomeSessionId } from './agent-home.mjs';
|
|
16
17
|
|
|
17
18
|
const ROOM_PREFIX = 'sess_room_';
|
|
@@ -41,6 +42,7 @@ const TOOL_SUMMARY_LIMIT = 400;
|
|
|
41
42
|
const ATTACHMENT_REF = /^[a-f0-9]{64}$/;
|
|
42
43
|
const PART_TYPES = new Set(['image', 'file', 'text']);
|
|
43
44
|
const ATTACHMENT_LIMIT = 64;
|
|
45
|
+
const SENDER_SESSION = /^[A-Za-z0-9_.:-]{1,200}$/;
|
|
44
46
|
|
|
45
47
|
export function isRoomId(value) {
|
|
46
48
|
return typeof value === 'string' && ROOM_ID.test(value);
|
|
@@ -138,6 +140,13 @@ function memberAt(text, members, boundary) {
|
|
|
138
140
|
return tie ? null : best;
|
|
139
141
|
}
|
|
140
142
|
|
|
143
|
+
/** The member `value` names exactly (its id or display name, any case); ''
|
|
144
|
+
* for none, or a name two members share. */
|
|
145
|
+
export function roomMemberNamed(value, members = []) {
|
|
146
|
+
const text = String(value ?? '').trim();
|
|
147
|
+
return (text && memberAt(text, members, /(?!)/)?.id) || '';
|
|
148
|
+
}
|
|
149
|
+
|
|
141
150
|
/** A user message to the room: `/ask <agent>[, <agent>…] <message>` addresses
|
|
142
151
|
* those members, in that order, by id or by name. `{ to, text }`, `to` the
|
|
143
152
|
* ordered member ids ([] for the whole room); `error` is 'member' (no such
|
|
@@ -283,7 +292,10 @@ export async function runRoomExchange({
|
|
|
283
292
|
stopped = () => false,
|
|
284
293
|
superseded = () => false,
|
|
285
294
|
waiting = true,
|
|
295
|
+
only = false,
|
|
286
296
|
}) {
|
|
297
|
+
// A room whose last member was removed has nobody to hear it: finished, no turns.
|
|
298
|
+
if (!members.length) return { outcome: 'done', rounds: 0, steps: 0, messages: 0 };
|
|
287
299
|
const targets = [...new Set((Array.isArray(to) ? to : [to]).filter((id) => members.includes(id)))];
|
|
288
300
|
const maxSteps = maxRounds * members.length;
|
|
289
301
|
const turns = [];
|
|
@@ -337,6 +349,9 @@ export async function runRoomExchange({
|
|
|
337
349
|
};
|
|
338
350
|
const answered = await callEach(targets);
|
|
339
351
|
if (answered) return answered;
|
|
352
|
+
// `only`: the called members answer and nobody else follows (a message
|
|
353
|
+
// from another session, a member relaying its own work).
|
|
354
|
+
if (only && targets.length) return end('done');
|
|
340
355
|
for (;;) {
|
|
341
356
|
let halt = halted();
|
|
342
357
|
if (halt) return halt;
|
|
@@ -456,8 +471,14 @@ export function roomTurnPrompt({
|
|
|
456
471
|
instruction = '',
|
|
457
472
|
scene = 'work',
|
|
458
473
|
waiting = rows.at(-1)?.kind === 'user' && !rows.at(-1)?.seen,
|
|
474
|
+
askedBy = '',
|
|
475
|
+
relay = '',
|
|
459
476
|
}) {
|
|
460
477
|
const userLast = rows.at(-1)?.kind === 'user';
|
|
478
|
+
// A relay of the member's own reported work, or a message that waits for
|
|
479
|
+
// an answer: no permission to pass.
|
|
480
|
+
const urged = waiting || Boolean(relay);
|
|
481
|
+
const asker = askedBy ? label(askedBy) : '';
|
|
461
482
|
const others = members.filter((member) => member.id !== self.id).map((member) => label(member.name));
|
|
462
483
|
// `addressed`: the members the user asked (or true for this member alone).
|
|
463
484
|
const asked = Array.isArray(addressed) ? addressed.length > 0 : addressed === true;
|
|
@@ -465,11 +486,15 @@ export function roomTurnPrompt({
|
|
|
465
486
|
const note = String(instruction ?? '').trim() ? label(instruction).slice(0, ROOM_INSTRUCTION_LIMIT) : '';
|
|
466
487
|
const lines = [
|
|
467
488
|
`[mixdog-runtime] Group room "${label(roomName)}". You are ${label(self.name)}; the other members are ${others.join(', ')}. The user and the members share this one room and members speak one at a time.`,
|
|
468
|
-
|
|
469
|
-
? '
|
|
470
|
-
:
|
|
471
|
-
? 'The user asked you directly: only
|
|
472
|
-
:
|
|
489
|
+
relay
|
|
490
|
+
? 'Results of work you started yourself (sessions you created or sent to) arrived; they follow the room lines below. Relay them to the room now, briefly and in your own words: only you speak this time.'
|
|
491
|
+
: askedWith
|
|
492
|
+
? 'The user asked you and other named members directly: only those members answer this time, one at a time.'
|
|
493
|
+
: asked
|
|
494
|
+
? asker
|
|
495
|
+
? `${asker} asked you directly from another session: only you answer this time.`
|
|
496
|
+
: 'The user asked you directly: only you answer this time.'
|
|
497
|
+
: 'It is your turn.',
|
|
473
498
|
...(asked
|
|
474
499
|
? [
|
|
475
500
|
'When the user wants another member involved, whatever name they use, write that member as @Name in your reply so the room can hand them a turn.',
|
|
@@ -491,9 +516,11 @@ export function roomTurnPrompt({
|
|
|
491
516
|
// so a message to the room never meets silence.
|
|
492
517
|
...(waiting
|
|
493
518
|
? [
|
|
494
|
-
|
|
495
|
-
? '
|
|
496
|
-
:
|
|
519
|
+
asker
|
|
520
|
+
? `${asker}'s message has no answer yet: reply to it now. Do not pass.`
|
|
521
|
+
: userLast
|
|
522
|
+
? 'The user just spoke and nobody has answered yet: reply to the user now, in their language. Do not pass.'
|
|
523
|
+
: "Nobody has answered the user's newest message yet (the member messages after it were written before it arrived): reply to that message now, in the user's language. Do not pass.",
|
|
497
524
|
]
|
|
498
525
|
: []),
|
|
499
526
|
...(scene === 'casual'
|
|
@@ -506,13 +533,13 @@ export function roomTurnPrompt({
|
|
|
506
533
|
]),
|
|
507
534
|
'Do not offer help or ask what the user needs unless they asked for help, and do not summarise the room or speak for the others.',
|
|
508
535
|
'Speak from your own role and the work you remember; never invent facts the user could rely on.',
|
|
509
|
-
|
|
536
|
+
urged
|
|
510
537
|
? 'Never say you have nothing to add or nothing to report; say something in character instead.'
|
|
511
538
|
: `Never say you have nothing to add or nothing to report; when you truly have nothing to say, reply exactly "${ROOM_PASS_REPLY}".`,
|
|
512
539
|
'Do not end every message with a question.',
|
|
513
540
|
]
|
|
514
541
|
: [
|
|
515
|
-
...(
|
|
542
|
+
...(urged
|
|
516
543
|
? []
|
|
517
544
|
: [
|
|
518
545
|
`Reply with your message to the room as plain text. Speak only to answer an open question, add relevant evidence, name a concrete disagreement or propose a useful next step; otherwise reply exactly "${ROOM_PASS_REPLY}".`,
|
|
@@ -527,7 +554,7 @@ export function roomTurnPrompt({
|
|
|
527
554
|
];
|
|
528
555
|
if (!rows.some((row) => !row.seen)) {
|
|
529
556
|
lines.push('Nothing new was said since your last turn.');
|
|
530
|
-
if (!
|
|
557
|
+
if (!urged) {
|
|
531
558
|
lines.push(
|
|
532
559
|
`This is not a direct call: speak only for what the lines above ask of you; otherwise reply exactly "${ROOM_PASS_REPLY}".`
|
|
533
560
|
);
|
|
@@ -578,12 +605,24 @@ export function roomTurnPrompt({
|
|
|
578
605
|
: rest.length
|
|
579
606
|
? `to you and ${rest.join(', ')} only`
|
|
580
607
|
: 'to you only';
|
|
581
|
-
|
|
608
|
+
// A message another session sent into the room names its real sender: a
|
|
609
|
+
// bot's words carry the trust note of every bot-to-bot message.
|
|
610
|
+
const who = row.fromName
|
|
611
|
+
? `${label(row.fromName)} (${row.fromAgent ? 'another bot' : "the user's Lead"}, from another session, not the user)`
|
|
612
|
+
: 'The user';
|
|
613
|
+
lines.push(
|
|
614
|
+
`${who}${row.seen ? ' (earlier, already shown to you)' : ''}, ${target}:`,
|
|
615
|
+
...(row.fromAgent ? [AGENT_MESSAGE_NOTE] : []),
|
|
616
|
+
String(row.text ?? '')
|
|
617
|
+
);
|
|
582
618
|
// The user's files follow their words, for the members the message reaches.
|
|
583
619
|
const files = !row.seen && (rest.length < targets.length || !targets.length) ? storedParts(row.parts) : [];
|
|
584
620
|
if (files.length) content.push({ type: 'text', text: lines.splice(0).join('\n') }, ...files);
|
|
585
621
|
}
|
|
586
622
|
if (open) lines.push('</room-transcript>');
|
|
623
|
+
// The member's reported work (agentTaskWakePrompt: runtime text, task
|
|
624
|
+
// output already escaped inside its own boundary).
|
|
625
|
+
if (relay) lines.push(String(relay));
|
|
587
626
|
if (!content.length) return lines.join('\n');
|
|
588
627
|
return lines.length ? [...content, { type: 'text', text: lines.join('\n') }] : content;
|
|
589
628
|
}
|
|
@@ -653,12 +692,19 @@ export function normalizeRoomRow(row = {}) {
|
|
|
653
692
|
const parts = carriesFiles ? storedParts(row.parts) : [];
|
|
654
693
|
const attachments = carriesFiles ? normalizeMessageAttachments(row.attachments) : [];
|
|
655
694
|
const images = row.kind === 'user' ? storedImages(row.images) : [];
|
|
695
|
+
// A user-kind row another session sent (session send to the room): its
|
|
696
|
+
// sender session, the sending bot (none for the user's Lead) and its name.
|
|
697
|
+
const fromSession =
|
|
698
|
+
row.kind === 'user' && typeof row.fromSession === 'string' && SENDER_SESSION.test(row.fromSession)
|
|
699
|
+
? row.fromSession
|
|
700
|
+
: '';
|
|
656
701
|
return {
|
|
657
702
|
id,
|
|
658
703
|
kind: row.kind,
|
|
659
704
|
...(Number.isFinite(row.at) ? { at: row.at } : {}),
|
|
660
705
|
...named('speaker'),
|
|
661
706
|
...named('name'),
|
|
707
|
+
...(fromSession ? { fromSession, ...named('fromAgent'), fromName: cleanName(row.fromName) || 'Lead' } : {}),
|
|
662
708
|
...(targets.length
|
|
663
709
|
? { to: targets.map((target) => target.id), toName: targets.map((target) => target.name || target.id) }
|
|
664
710
|
: {}),
|
|
@@ -730,9 +776,19 @@ export function roomRowItem(message, id) {
|
|
|
730
776
|
// A user row's (or member speech's) files, in the fields a chat prompt's row draws them from.
|
|
731
777
|
...(room.kind === 'user' || room.kind === 'speech' ? transcriptAttachmentFields(message.meta.transcript) : {}),
|
|
732
778
|
...(room.kind === 'user' && Array.isArray(images) && images.length ? { images } : {}),
|
|
779
|
+
...roomRowSender(room),
|
|
733
780
|
};
|
|
734
781
|
}
|
|
735
782
|
|
|
783
|
+
/** The attribution a user-kind room row from another session shows on its
|
|
784
|
+
* bubble, in the fields a chat prompt row has: the sending bot
|
|
785
|
+
* (`fromAgent`), else the user's Lead (`sender: 'lead'`); none for the
|
|
786
|
+
* user's own message. */
|
|
787
|
+
export function roomRowSender(room) {
|
|
788
|
+
if (room?.kind !== 'user' || !room.fromSession) return {};
|
|
789
|
+
return room.fromAgent ? { fromAgent: room.fromAgent } : { sender: 'lead' };
|
|
790
|
+
}
|
|
791
|
+
|
|
736
792
|
/** The room rows of a transcript (`{ room }` items) as row descriptors, in order. */
|
|
737
793
|
export function roomRowsOfItems(items) {
|
|
738
794
|
const rows = [];
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
isProtectedContextAckMessage,
|
|
3
|
+
latestActualUserInstructionIndex,
|
|
4
|
+
pendingTaskNotificationIndexes,
|
|
5
|
+
} from './messages.mjs';
|
|
2
6
|
|
|
3
7
|
export const ACTIVE_TURN_CONTINUATION_SOURCE = 'compact-active-turn-continuation';
|
|
4
8
|
export const ACTIVE_TURN_CONTINUATION_ANCHOR = '<active-turn-continuation>';
|
|
@@ -22,7 +26,7 @@ function makeActiveTurnContinuationMessage() {
|
|
|
22
26
|
content: [
|
|
23
27
|
'<system-reminder>',
|
|
24
28
|
ACTIVE_TURN_CONTINUATION_ANCHOR,
|
|
25
|
-
'The latest real user request was already in progress when Compact ran.',
|
|
29
|
+
'The latest real user request or task notification was already in progress when Compact ran.',
|
|
26
30
|
'The handoff and retained execution records above describe progress so far. A plan is not evidence that a tool succeeded; use the recorded outcomes.',
|
|
27
31
|
'Continue from that state without repeating progress messages or completed tool calls.',
|
|
28
32
|
'Use tools only for incomplete or failed work, or to recover missing evidence from an execution archive.',
|
|
@@ -38,10 +42,15 @@ function makeActiveTurnContinuationMessage() {
|
|
|
38
42
|
|
|
39
43
|
export function activeTurnContinuationMessage(messages) {
|
|
40
44
|
const list = Array.isArray(messages) ? messages : [];
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
+
// The turn's latest input: a real user request or an unanswered task
|
|
46
|
+
// notification. Only execution after it is progress of the current turn.
|
|
47
|
+
const latestInputIndex = Math.max(
|
|
48
|
+
latestActualUserInstructionIndex(list),
|
|
49
|
+
pendingTaskNotificationIndexes(list).at(-1) ?? -1
|
|
50
|
+
);
|
|
51
|
+
if (latestInputIndex < 0) return null;
|
|
52
|
+
const afterLatestInput = list.slice(latestInputIndex + 1);
|
|
53
|
+
if (!afterLatestInput.some(isExistingContinuation) && !afterLatestInput.some(isExecutionActivity)) {
|
|
45
54
|
return null;
|
|
46
55
|
}
|
|
47
56
|
return makeActiveTurnContinuationMessage();
|