blun-king-cli 9.1.485 → 9.1.488
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/LIESMICH.txt +33 -17
- package/README.md +20 -8
- package/agent-spine-plugin/.claude-plugin/marketplace.json +20 -0
- package/agent-spine-plugin/.claude-plugin/plugin.json +14 -0
- package/agent-spine-plugin/.codex-plugin/plugin.json +34 -0
- package/agent-spine-plugin/.mcp.json +8 -0
- package/agent-spine-plugin/CHANGELOG.md +277 -0
- package/agent-spine-plugin/LICENSE +186 -0
- package/agent-spine-plugin/README.md +342 -0
- package/agent-spine-plugin/assets/agentspine-banner.svg +32 -0
- package/agent-spine-plugin/bin/agentspine-mcp.js +4 -0
- package/agent-spine-plugin/bin/agentspine.js +7 -0
- package/agent-spine-plugin/blun.plugin.json +33 -0
- package/agent-spine-plugin/docs/acceptance.md +61 -0
- package/agent-spine-plugin/docs/architecture.md +183 -0
- package/agent-spine-plugin/docs/attention.md +121 -0
- package/agent-spine-plugin/docs/automatic-continuity.md +79 -0
- package/agent-spine-plugin/docs/channel-runtime.md +92 -0
- package/agent-spine-plugin/docs/coordination.md +138 -0
- package/agent-spine-plugin/docs/feed-transport.md +99 -0
- package/agent-spine-plugin/docs/gateway-runtime.md +109 -0
- package/agent-spine-plugin/docs/harness-reference.md +45 -0
- package/agent-spine-plugin/docs/host-integration.md +129 -0
- package/agent-spine-plugin/docs/https-transport.md +116 -0
- package/agent-spine-plugin/docs/learning.md +98 -0
- package/agent-spine-plugin/docs/object-transport.md +93 -0
- package/agent-spine-plugin/docs/peer-transport.md +88 -0
- package/agent-spine-plugin/docs/preservation-contract.md +53 -0
- package/agent-spine-plugin/docs/quality-gates.md +50 -0
- package/agent-spine-plugin/docs/relationships.md +67 -0
- package/agent-spine-plugin/docs/releasing.md +83 -0
- package/agent-spine-plugin/docs/roadmap.md +307 -0
- package/agent-spine-plugin/docs/selfstarter.md +88 -0
- package/agent-spine-plugin/docs/session-briefing.md +74 -0
- package/agent-spine-plugin/docs/shared-memory.md +259 -0
- package/agent-spine-plugin/docs/source-roots.md +86 -0
- package/agent-spine-plugin/docs/sqlite-transport.md +76 -0
- package/agent-spine-plugin/hooks/hooks.json +95 -0
- package/agent-spine-plugin/hooks/version.json +5 -0
- package/agent-spine-plugin/package.json +69 -0
- package/agent-spine-plugin/scripts/check-hosts.js +193 -0
- package/agent-spine-plugin/scripts/check-install.js +551 -0
- package/agent-spine-plugin/scripts/check-syntax.js +29 -0
- package/agent-spine-plugin/scripts/github-actions.js +11 -0
- package/agent-spine-plugin/scripts/release-check.js +127 -0
- package/agent-spine-plugin/scripts/run-acceptance.js +19 -0
- package/agent-spine-plugin/scripts/run-checks.js +46 -0
- package/agent-spine-plugin/scripts/run-tests-hermetic.js +73 -0
- package/agent-spine-plugin/skill/SKILL.md +76 -0
- package/agent-spine-plugin/skills/agent-spine/SKILL.md +84 -0
- package/agent-spine-plugin/spine-example/1-identity.md +12 -0
- package/agent-spine-plugin/spine-example/2-voice.md +6 -0
- package/agent-spine-plugin/spine-example/3-conduct.md +8 -0
- package/agent-spine-plugin/spine-example/4-history.md +4 -0
- package/agent-spine-plugin/src/cli.js +950 -0
- package/agent-spine-plugin/src/hook.js +646 -0
- package/agent-spine-plugin/src/index.js +84 -0
- package/agent-spine-plugin/src/lib/acceptance.js +294 -0
- package/agent-spine-plugin/src/lib/attention.js +755 -0
- package/agent-spine-plugin/src/lib/audit.js +308 -0
- package/agent-spine-plugin/src/lib/authentication.js +513 -0
- package/agent-spine-plugin/src/lib/briefing.js +305 -0
- package/agent-spine-plugin/src/lib/catalog.js +142 -0
- package/agent-spine-plugin/src/lib/channel-runtime.js +665 -0
- package/agent-spine-plugin/src/lib/context.js +154 -0
- package/agent-spine-plugin/src/lib/continuity.js +333 -0
- package/agent-spine-plugin/src/lib/coordination.js +577 -0
- package/agent-spine-plugin/src/lib/documents.js +199 -0
- package/agent-spine-plugin/src/lib/feed-transport.js +497 -0
- package/agent-spine-plugin/src/lib/filesystem-retry.js +27 -0
- package/agent-spine-plugin/src/lib/gateway-runtime.js +760 -0
- package/agent-spine-plugin/src/lib/graph.js +284 -0
- package/agent-spine-plugin/src/lib/https-transport.js +392 -0
- package/agent-spine-plugin/src/lib/indexed-memory-offline.js +40 -0
- package/agent-spine-plugin/src/lib/indexed-memory.js +281 -0
- package/agent-spine-plugin/src/lib/learning.js +645 -0
- package/agent-spine-plugin/src/lib/object-transport.js +206 -0
- package/agent-spine-plugin/src/lib/paths.js +60 -0
- package/agent-spine-plugin/src/lib/peer-transport.js +283 -0
- package/agent-spine-plugin/src/lib/persona-runtime.js +503 -0
- package/agent-spine-plugin/src/lib/runtime.js +13 -0
- package/agent-spine-plugin/src/lib/selfstarter.js +819 -0
- package/agent-spine-plugin/src/lib/sharing.js +969 -0
- package/agent-spine-plugin/src/lib/source-roots.js +451 -0
- package/agent-spine-plugin/src/lib/sqlite-transport.js +501 -0
- package/agent-spine-plugin/src/lib/telegram-adapter.js +119 -0
- package/agent-spine-plugin/src/lib/voice-runtime.js +39 -0
- package/agent-spine-plugin/src/mcp.js +526 -0
- package/agent-spine-plugin/src/version.js +1 -0
- package/agent-spine-plugin/src/worker.js +178 -0
- package/bin/identity-context-policy.cjs +51 -1
- package/bin/launcher-runtime.js +22 -1
- package/bin/plugin-bootstrap.js +68 -27
- package/bin/startup-preferences.cjs +130 -0
- package/bin/turn-thinking-policy.cjs +5 -4
- package/bin/turn-tool-performance-policy.cjs +15 -2
- package/bin/update-notice.js +104 -43
- package/blun.mjs +108 -32
- package/package.json +2 -1
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { watch } from "node:fs";
|
|
4
|
+
import { realpath } from "node:fs/promises";
|
|
5
|
+
import { isAbsolute, resolve } from "node:path";
|
|
6
|
+
import { claimGatewayWork, completeGatewayRun, deliverPrepared, failGatewayRun, loadGatewayRuntime, reconcileGateway, updateGatewayHealth } from "./lib/gateway-runtime.js";
|
|
7
|
+
import { createTelegramAdapter } from "./lib/telegram-adapter.js";
|
|
8
|
+
import { acknowledgeChannelDelivery, loadChannelRuntime } from "./lib/channel-runtime.js";
|
|
9
|
+
import { loadPersonaRuntime, syncPersonaRosterFromEnvironment } from "./lib/persona-runtime.js";
|
|
10
|
+
import { isMainModule } from "./lib/runtime.js";
|
|
11
|
+
|
|
12
|
+
const MAX_FRAME = 64 * 1024;
|
|
13
|
+
const WAKE_FILES = new Set(["attention.json", "channel-runtime.json", "gateway-policy.json", "persona-policy.json", "persona-runtime.json"]);
|
|
14
|
+
|
|
15
|
+
export async function waitForGatewayWake(root, delayMs, { watchFactory = watch, realpathFactory = realpath } = {}) {
|
|
16
|
+
const delay = Math.max(250, Math.min(60000, Number(delayMs) || 60000));
|
|
17
|
+
const { directory } = await loadGatewayRuntime(root);
|
|
18
|
+
let watchPath;
|
|
19
|
+
try { watchPath = await realpathFactory(directory); } catch { return "watch-unavailable"; }
|
|
20
|
+
return new Promise((resolvePromise) => {
|
|
21
|
+
let settled = false; let watcher;
|
|
22
|
+
const finish = (reason) => {
|
|
23
|
+
if (settled) return;
|
|
24
|
+
settled = true; clearTimeout(timer);
|
|
25
|
+
watcher?.close();
|
|
26
|
+
resolvePromise(reason);
|
|
27
|
+
};
|
|
28
|
+
const timer = setTimeout(() => finish("timer"), delay);
|
|
29
|
+
try {
|
|
30
|
+
watcher = watchFactory(watchPath, { persistent: true }, (_eventType, filename) => {
|
|
31
|
+
const name = filename === null ? null : String(filename);
|
|
32
|
+
if (name === null || WAKE_FILES.has(name)) finish("event");
|
|
33
|
+
});
|
|
34
|
+
watcher.on?.("error", () => finish("watch-error"));
|
|
35
|
+
} catch { finish("watch-unavailable"); }
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function exactRunner(env) {
|
|
40
|
+
const runner = env.AGENTSPINE_HOST_RUNNER;
|
|
41
|
+
if (!runner || !isAbsolute(runner)) throw new Error("AGENTSPINE_HOST_RUNNER must be an absolute locally approved executable path");
|
|
42
|
+
return resolve(runner);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function invokeHostRunner(item, { env = process.env, timeoutMs = 300000, spawnProcess = spawn } = {}) {
|
|
46
|
+
const executable = exactRunner(env);
|
|
47
|
+
return new Promise((resolvePromise, reject) => {
|
|
48
|
+
const child = spawnProcess(executable, [], {
|
|
49
|
+
shell: false, windowsHide: true, stdio: ["pipe", "pipe", "pipe"],
|
|
50
|
+
env: Object.fromEntries(["PATH", "Path", "HOME", "USERPROFILE", "SystemRoot", "SYSTEMROOT", "WINDIR",
|
|
51
|
+
"AGENTSPINE_STATE_DIR", "CLAUDE_CONFIG_DIR", "CODEX_HOME"].filter((key) => env[key] !== undefined).map((key) => [key, env[key]]))
|
|
52
|
+
});
|
|
53
|
+
let stdout = Buffer.alloc(0); let stderr = Buffer.alloc(0); let settled = false;
|
|
54
|
+
const finish = (error, value) => { if (settled) return; settled = true; clearTimeout(timer); error ? reject(error) : resolvePromise(value); };
|
|
55
|
+
child.stdout.on("data", (chunk) => { stdout = Buffer.concat([stdout, chunk]); if (stdout.length > MAX_FRAME) child.kill(); });
|
|
56
|
+
child.stderr.on("data", (chunk) => { stderr = Buffer.concat([stderr, chunk]).subarray(0, 2048); });
|
|
57
|
+
child.on("error", (error) => finish(error));
|
|
58
|
+
child.on("close", (code) => {
|
|
59
|
+
if (stdout.length > MAX_FRAME) return finish(new Error("host runner response exceeds 64 KiB"));
|
|
60
|
+
if (code !== 0) return finish(new Error("host runner failed: " + stderr.toString("utf8").slice(0, 2048)));
|
|
61
|
+
try { finish(null, JSON.parse(stdout.toString("utf8"))); } catch { finish(new Error("host runner returned invalid JSON")); }
|
|
62
|
+
});
|
|
63
|
+
const timer = setTimeout(() => { child.kill(); finish(new Error("host runner exceeded its bounded timeout")); }, timeoutMs);
|
|
64
|
+
child.stdin.end(JSON.stringify({ schema: "agentspine.run-request/v1", item, authority: "execution-state-only" }) + "\n");
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function hostWorkItem(root, item) {
|
|
69
|
+
const [{ policy }, personas, channel] = await Promise.all([
|
|
70
|
+
loadGatewayRuntime(root), loadPersonaRuntime(root), loadChannelRuntime(root)
|
|
71
|
+
]);
|
|
72
|
+
const persona = personas.runtime.personas.find((entry) => entry.personaId === item.agentId && entry.status === "active");
|
|
73
|
+
const identity = personas.policy.bindings.find((entry) => entry.id === persona?.bindingId && entry.active);
|
|
74
|
+
if (!persona || !identity) throw new Error("claimed work lost its authenticated agent identity");
|
|
75
|
+
const goal = item.goalId === null ? null : policy.goals.find((entry) => entry.goalId === item.goalId) || null;
|
|
76
|
+
const event = item.channelEventId === null ? null
|
|
77
|
+
: channel.runtime.events.find((entry) => entry.eventId === item.channelEventId) || null;
|
|
78
|
+
if (item.channelEventId && !event) throw new Error("claimed channel work lost its exact event");
|
|
79
|
+
return {
|
|
80
|
+
...structuredClone(item), host: identity.host, profileId: identity.profileId, projectRoot: root,
|
|
81
|
+
goal: goal ? structuredClone(goal) : null,
|
|
82
|
+
hostEnvironment: {
|
|
83
|
+
AGENTSPINE_GATEWAY_CONTEXT: "agentspine.gateway-start/v1",
|
|
84
|
+
AGENTSPINE_ENTITY_ID: item.agentId,
|
|
85
|
+
AGENTSPINE_PROJECT_ID: item.projectId,
|
|
86
|
+
...(item.groupId === null ? {} : { AGENTSPINE_GROUP_ID: item.groupId }),
|
|
87
|
+
...(event ? {
|
|
88
|
+
AGENTSPINE_CHANNEL_EVENT_ID: event.eventId,
|
|
89
|
+
AGENTSPINE_CHANNEL_PROVIDER: event.provider
|
|
90
|
+
} : {})
|
|
91
|
+
},
|
|
92
|
+
channelStart: event ? {
|
|
93
|
+
agent_spine_scope: { entity_id: event.agentId, project_id: event.projectId, group_id: event.groupId },
|
|
94
|
+
agent_spine_channel_event: { event_id: event.eventId, provider: event.provider },
|
|
95
|
+
session_key: event.sessionKey,
|
|
96
|
+
authority: "explicit-local-channel-policy"
|
|
97
|
+
} : null
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function runWorkerTick({ root = process.cwd(), workerId = "gateway-worker:local", now = new Date(),
|
|
102
|
+
hostRunner = invokeHostRunner, adapter = null, env = process.env } = {}) {
|
|
103
|
+
const deliveryAdapter = adapter || createTelegramAdapter({ root, env });
|
|
104
|
+
await syncPersonaRosterFromEnvironment({ root, env, now });
|
|
105
|
+
const initial = await loadGatewayRuntime(root);
|
|
106
|
+
if (!initial.policy.enabled || initial.policy.killSwitch) return { status: "stopped", processed: false };
|
|
107
|
+
if (typeof deliveryAdapter.poll === "function") {
|
|
108
|
+
try { await deliveryAdapter.poll(); }
|
|
109
|
+
catch (error) {
|
|
110
|
+
await updateGatewayHealth({ root, worker: "degraded", adapter: "failed", now });
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
await reconcileGateway({ root, now });
|
|
115
|
+
await updateGatewayHealth({ root, worker: "healthy", adapter: "healthy", now });
|
|
116
|
+
const afterReconcile = await loadGatewayRuntime(root);
|
|
117
|
+
const dueOutbox = afterReconcile.runtime.outbox.filter((item) => ["prepared", "failed"].includes(item.status)
|
|
118
|
+
&& new Date(item.nextAttemptAt) <= new Date(now)).sort((a, b) => a.nextAttemptAt.localeCompare(b.nextAttemptAt)
|
|
119
|
+
|| a.createdAt.localeCompare(b.createdAt) || a.outboxId.localeCompare(b.outboxId))[0] || null;
|
|
120
|
+
if (dueOutbox) {
|
|
121
|
+
const delivery = await deliverPrepared({ root, outboxId: dueOutbox.outboxId, adapter: deliveryAdapter, now });
|
|
122
|
+
if (delivery.outbox.status === "delivered") await acknowledgeChannelDelivery({ root,
|
|
123
|
+
eventId: delivery.outbox.eventId, bindingId: delivery.outbox.bindingId,
|
|
124
|
+
deliveryReceiptId: delivery.receipt.id, now });
|
|
125
|
+
return { status: delivery.outbox.status, processed: true, outboxId: delivery.outbox.outboxId,
|
|
126
|
+
recoveredDelivery: true };
|
|
127
|
+
}
|
|
128
|
+
const claim = await claimGatewayWork({ root, workerId, now });
|
|
129
|
+
if (!claim.item) return { status: claim.reason, processed: false };
|
|
130
|
+
let result;
|
|
131
|
+
try { result = await hostRunner(await hostWorkItem(root, claim.item), { env }); }
|
|
132
|
+
catch (error) {
|
|
133
|
+
const failed = await failGatewayRun({ root, queueId: claim.item.queueId, workerId,
|
|
134
|
+
error: "Host runtime unavailable: " + String(error.message).slice(0, 400), now });
|
|
135
|
+
return { status: failed.item.status, processed: true, queueId: failed.item.queueId, retryAt: failed.item.availableAt };
|
|
136
|
+
}
|
|
137
|
+
const completed = await completeGatewayRun({ root, queueId: claim.item.queueId, workerId, result, now });
|
|
138
|
+
if (!completed.outbox) return { status: completed.item.status, processed: true, queueId: completed.item.queueId };
|
|
139
|
+
const delivery = await deliverPrepared({ root, outboxId: completed.outbox.outboxId,
|
|
140
|
+
adapter: deliveryAdapter, now });
|
|
141
|
+
if (delivery.outbox.status === "delivered") await acknowledgeChannelDelivery({ root,
|
|
142
|
+
eventId: delivery.outbox.eventId, bindingId: delivery.outbox.bindingId,
|
|
143
|
+
deliveryReceiptId: delivery.receipt.id, now });
|
|
144
|
+
return { status: delivery.outbox.status, processed: true, queueId: completed.item.queueId, outboxId: delivery.outbox.outboxId };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function runWorker({ root = process.cwd(), once = false, env = process.env,
|
|
148
|
+
waitForWake = waitForGatewayWake } = {}) {
|
|
149
|
+
const workerId = "gateway-worker:" + process.pid;
|
|
150
|
+
do {
|
|
151
|
+
const result = await runWorkerTick({ root, workerId, env });
|
|
152
|
+
if (once) return result;
|
|
153
|
+
const { policy, runtime } = await loadGatewayRuntime(root);
|
|
154
|
+
if (!policy.enabled || policy.killSwitch) return { status: "stopped", processed: false };
|
|
155
|
+
const pending = [
|
|
156
|
+
...runtime.queue.filter((item) => item.status === "pending").map((item) => new Date(item.availableAt).getTime()),
|
|
157
|
+
...runtime.outbox.filter((item) => ["prepared", "failed"].includes(item.status))
|
|
158
|
+
.map((item) => new Date(item.nextAttemptAt).getTime())
|
|
159
|
+
];
|
|
160
|
+
const delay = pending.length ? Math.max(250, Math.min(60000, Math.min(...pending) - Date.now())) : 60000;
|
|
161
|
+
await waitForWake(root, delay);
|
|
162
|
+
} while (true);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (isMainModule(import.meta.url)) {
|
|
166
|
+
const args = process.argv.slice(2); const once = args.includes("--once");
|
|
167
|
+
const rootIndex = args.indexOf("--root");
|
|
168
|
+
if (rootIndex >= 0 && (!args[rootIndex + 1] || args[rootIndex + 1].startsWith("--"))) throw new Error("--root requires a path");
|
|
169
|
+
const root = rootIndex >= 0 ? args[rootIndex + 1] : process.cwd();
|
|
170
|
+
const rosterIndex = args.indexOf("--persona-roster");
|
|
171
|
+
if (rosterIndex >= 0 && (!args[rosterIndex + 1] || args[rosterIndex + 1].startsWith("--"))) {
|
|
172
|
+
throw new Error("--persona-roster requires a path");
|
|
173
|
+
}
|
|
174
|
+
const env = { ...process.env };
|
|
175
|
+
if (rosterIndex >= 0) env.AGENTSPINE_PERSONA_ROSTER_FILE = args[rosterIndex + 1];
|
|
176
|
+
runWorker({ root, once, env }).then((result) => process.stdout.write(JSON.stringify(result) + "\n"))
|
|
177
|
+
.catch((error) => { process.stderr.write("AgentSpine worker: " + String(error.message).slice(0, 2048) + "\n"); process.exitCode = 1; });
|
|
178
|
+
}
|
|
@@ -21,6 +21,7 @@ const ACTIVITY_FILE_RE = /^\d{4}-\d{2}-\d{2}\.json$/u;
|
|
|
21
21
|
const RELATIONSHIP_CANDIDATE_FILE_RE = /^[a-f0-9]{32}\.json$/u;
|
|
22
22
|
const ACTOR_FILE_RE = /^([A-Za-z0-9][A-Za-z0-9._-]{0,127})\.json$/u;
|
|
23
23
|
const LONG_GAP_MS = 7 * 24 * 60 * 60 * 1000;
|
|
24
|
+
const ESTABLISHED_INTERACTION_DAYS = 3;
|
|
24
25
|
const MAX_COLLEAGUES_IN_CONTEXT = 12;
|
|
25
26
|
|
|
26
27
|
function autoGraphEnabled(env) {
|
|
@@ -468,6 +469,52 @@ function recordInteractionActivity(root, agentId, actorId, interactedAt) {
|
|
|
468
469
|
);
|
|
469
470
|
}
|
|
470
471
|
|
|
472
|
+
function trustedInteractionDayCount(root, agentId, actorId, limit) {
|
|
473
|
+
if (!actorId) return 0;
|
|
474
|
+
const activityRoot = path.resolve(root, 'agents', agentId, 'activity', actorId);
|
|
475
|
+
if (!isInside(root, activityRoot)) return 0;
|
|
476
|
+
try {
|
|
477
|
+
const stat = fs.lstatSync(activityRoot);
|
|
478
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) return 0;
|
|
479
|
+
if (!isInside(root, fs.realpathSync(activityRoot))) return 0;
|
|
480
|
+
let count = 0;
|
|
481
|
+
for (const name of fs.readdirSync(activityRoot).filter((entry) => ACTIVITY_FILE_RE.test(entry)).sort()) {
|
|
482
|
+
const entry = readJson(root, ['agents', agentId, 'activity', actorId, name]);
|
|
483
|
+
const interactedAt = trustedTimestamp(entry?.interacted_at);
|
|
484
|
+
if (entry?.version !== 1
|
|
485
|
+
|| entry?.actor_id !== actorId
|
|
486
|
+
|| entry?.source !== 'trusted_channel_metadata'
|
|
487
|
+
|| !interactedAt
|
|
488
|
+
|| activityDate(interactedAt) !== name.slice(0, 10)) continue;
|
|
489
|
+
count += 1;
|
|
490
|
+
if (count >= limit) break;
|
|
491
|
+
}
|
|
492
|
+
return count;
|
|
493
|
+
} catch {
|
|
494
|
+
return 0;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function promoteReturningRelationship(root, agentId, actorId) {
|
|
499
|
+
const relationship = readJson(root, ['agents', agentId, 'relationships', `${actorId}.json`]);
|
|
500
|
+
if (relationship?.version !== 1
|
|
501
|
+
|| relationship.actor_id !== actorId
|
|
502
|
+
|| relationship.status !== 'new') return false;
|
|
503
|
+
if (trustedInteractionDayCount(root, agentId, actorId, ESTABLISHED_INTERACTION_DAYS)
|
|
504
|
+
< ESTABLISHED_INTERACTION_DAYS) return false;
|
|
505
|
+
return updateJsonAtomically(
|
|
506
|
+
root,
|
|
507
|
+
['agents', agentId, 'relationships', `${actorId}.json`],
|
|
508
|
+
(relationship) => (
|
|
509
|
+
relationship.version === 1
|
|
510
|
+
&& relationship.actor_id === actorId
|
|
511
|
+
&& relationship.status === 'new'
|
|
512
|
+
? { ...relationship, status: 'established' }
|
|
513
|
+
: undefined
|
|
514
|
+
),
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
|
|
471
518
|
function previousInteractionAfterLongGap(root, agentId, actorId) {
|
|
472
519
|
if (!actorId) return undefined;
|
|
473
520
|
const activityRoot = path.resolve(root, 'agents', agentId, 'activity', actorId);
|
|
@@ -598,7 +645,10 @@ function recordChannelIdentity(envelope, env = process.env) {
|
|
|
598
645
|
messageId: safeId(meta.message_id),
|
|
599
646
|
receivedAt,
|
|
600
647
|
});
|
|
601
|
-
if (firstSeenAt)
|
|
648
|
+
if (firstSeenAt) {
|
|
649
|
+
recordInteractionActivity(root, agentId, actorId, firstSeenAt);
|
|
650
|
+
if (promoteReturningRelationship(root, agentId, actorId)) updated.push('relationship_status');
|
|
651
|
+
}
|
|
602
652
|
let relationshipMemory;
|
|
603
653
|
let relationshipMemoryUnavailable = false;
|
|
604
654
|
let relationshipNotes = [];
|
package/bin/launcher-runtime.js
CHANGED
|
@@ -18,7 +18,10 @@ const {
|
|
|
18
18
|
launcherHelpText,
|
|
19
19
|
launcherModeFromArgv,
|
|
20
20
|
} = require('./launcher-mode');
|
|
21
|
-
const {
|
|
21
|
+
const {
|
|
22
|
+
installManagedAgentSpinePlugin,
|
|
23
|
+
installManagedTelegramPlugin,
|
|
24
|
+
} = require('./plugin-bootstrap');
|
|
22
25
|
const {
|
|
23
26
|
availableStandardToolNames,
|
|
24
27
|
seedConfiguredMnemoMcp,
|
|
@@ -42,6 +45,11 @@ const {
|
|
|
42
45
|
resolveProfilePaths,
|
|
43
46
|
seedDefaultProfileFromLegacy,
|
|
44
47
|
} = require('./profile-runtime.cjs');
|
|
48
|
+
const {
|
|
49
|
+
migrateLegacyStartupPreferences,
|
|
50
|
+
readStartupPreferences,
|
|
51
|
+
resolveStartupUiLocale,
|
|
52
|
+
} = require('./startup-preferences.cjs');
|
|
45
53
|
|
|
46
54
|
const PKG = path.resolve(__dirname, '..');
|
|
47
55
|
const RAW_ARGS = process.argv.slice(2);
|
|
@@ -270,6 +278,10 @@ function installTelegramForProfile(packageRoot, blunDir) {
|
|
|
270
278
|
}
|
|
271
279
|
}
|
|
272
280
|
|
|
281
|
+
function installAgentSpineForProfile(packageRoot, blunDir) {
|
|
282
|
+
installManagedAgentSpinePlugin({ packageRoot, blunDir });
|
|
283
|
+
}
|
|
284
|
+
|
|
273
285
|
function createManagedNodeEnvironment(binary, env = process.env, platform = process.platform) {
|
|
274
286
|
const pathImpl = platform === 'win32' ? path.win32 : path.posix;
|
|
275
287
|
const separator = platform === 'win32' ? path.win32.delimiter : path.posix.delimiter;
|
|
@@ -398,6 +410,9 @@ async function runLauncher(options = {}) {
|
|
|
398
410
|
if (explicitUpdateRequest) {
|
|
399
411
|
const privatePaths = getPrivatePaths();
|
|
400
412
|
ensurePrivateDirectory(privatePaths.sharedHome);
|
|
413
|
+
ensurePrivateDirectory(privatePaths.profilePaths.home);
|
|
414
|
+
migrateLegacyStartupPreferences(privatePaths.profilePaths.home);
|
|
415
|
+
const startupPreferences = readStartupPreferences(privatePaths.profilePaths.home);
|
|
401
416
|
const result = await runExplicitUpdate({
|
|
402
417
|
currentVersion: readPackageVersion(),
|
|
403
418
|
env: process.env,
|
|
@@ -405,6 +420,7 @@ async function runLauncher(options = {}) {
|
|
|
405
420
|
stdout: process.stdout,
|
|
406
421
|
stderr: process.stderr,
|
|
407
422
|
blunDir: privatePaths.sharedHome,
|
|
423
|
+
uiLocale: resolveStartupUiLocale(startupPreferences.uiLocale, process.env),
|
|
408
424
|
packageRoot: PKG,
|
|
409
425
|
noticeLease: launcherLease,
|
|
410
426
|
});
|
|
@@ -449,6 +465,8 @@ async function runLauncher(options = {}) {
|
|
|
449
465
|
}
|
|
450
466
|
const blunDir = profilePaths.home;
|
|
451
467
|
ensurePrivateDirectory(blunDir);
|
|
468
|
+
migrateLegacyStartupPreferences(blunDir);
|
|
469
|
+
const startupPreferences = readStartupPreferences(blunDir);
|
|
452
470
|
|
|
453
471
|
const updateResult = await runUpdateNotice({
|
|
454
472
|
currentVersion: readPackageVersion(),
|
|
@@ -458,6 +476,8 @@ async function runLauncher(options = {}) {
|
|
|
458
476
|
stdout: process.stdout,
|
|
459
477
|
stderr: process.stderr,
|
|
460
478
|
blunDir: privatePaths.sharedHome,
|
|
479
|
+
autoInstall: startupPreferences.autoInstall,
|
|
480
|
+
uiLocale: resolveStartupUiLocale(startupPreferences.uiLocale, process.env),
|
|
461
481
|
packageRoot: PKG,
|
|
462
482
|
noticeLease: launcherLease,
|
|
463
483
|
});
|
|
@@ -491,6 +511,7 @@ async function runLauncher(options = {}) {
|
|
|
491
511
|
});
|
|
492
512
|
seedConfiguredMnemoMcp({ blunDir, env: process.env });
|
|
493
513
|
seedStandardSkills({ packageRoot: PKG, blunDir: privatePaths.sharedHome });
|
|
514
|
+
installAgentSpineForProfile(PKG, blunDir);
|
|
494
515
|
if (mode === LAUNCHER_MODES.KING) installTelegramForProfile(PKG, blunDir);
|
|
495
516
|
|
|
496
517
|
const nativeModules = repairConfiguredNativeModules({
|
package/bin/plugin-bootstrap.js
CHANGED
|
@@ -75,14 +75,14 @@ function pluginTreeDigest(root) {
|
|
|
75
75
|
return hash.digest('hex');
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
function
|
|
79
|
-
const records = installed.plugins.filter((plugin) => plugin.id ===
|
|
78
|
+
function hasCurrentPluginRecord(installed, pluginRoot, pluginId, mcpServerName) {
|
|
79
|
+
const records = installed.plugins.filter((plugin) => plugin.id === pluginId);
|
|
80
80
|
if (records.length !== 1) return false;
|
|
81
|
-
const [
|
|
82
|
-
return
|
|
83
|
-
&&
|
|
84
|
-
&&
|
|
85
|
-
&&
|
|
81
|
+
const [plugin] = records;
|
|
82
|
+
return plugin.root === pluginRoot
|
|
83
|
+
&& plugin.source === 'local-path'
|
|
84
|
+
&& plugin.enabled === true
|
|
85
|
+
&& plugin.capabilities?.mcpServers?.[mcpServerName]?.enabled === true;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
function readInstalledPlugins(filePath) {
|
|
@@ -96,16 +96,16 @@ function readInstalledPlugins(filePath) {
|
|
|
96
96
|
return parsed;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
function updatedInstalledPlugins(installed, pluginRoot, now) {
|
|
100
|
-
const previous = installed.plugins.find((plugin) => plugin.id ===
|
|
99
|
+
function updatedInstalledPlugins(installed, pluginRoot, now, pluginId, mcpServerName) {
|
|
100
|
+
const previous = installed.plugins.find((plugin) => plugin.id === pluginId);
|
|
101
101
|
const previousCapabilities = isObject(previous?.capabilities) ? previous.capabilities : {};
|
|
102
102
|
const previousMcpServers = isObject(previousCapabilities.mcpServers)
|
|
103
103
|
? previousCapabilities.mcpServers : {};
|
|
104
|
-
const
|
|
105
|
-
? previousMcpServers
|
|
106
|
-
const
|
|
104
|
+
const previousMcp = isObject(previousMcpServers[mcpServerName])
|
|
105
|
+
? previousMcpServers[mcpServerName] : {};
|
|
106
|
+
const updatedPlugin = {
|
|
107
107
|
...previous,
|
|
108
|
-
id:
|
|
108
|
+
id: pluginId,
|
|
109
109
|
root: pluginRoot,
|
|
110
110
|
source: 'local-path',
|
|
111
111
|
enabled: true,
|
|
@@ -116,7 +116,7 @@ function updatedInstalledPlugins(installed, pluginRoot, now) {
|
|
|
116
116
|
...previousCapabilities,
|
|
117
117
|
mcpServers: {
|
|
118
118
|
...previousMcpServers,
|
|
119
|
-
|
|
119
|
+
[mcpServerName]: { ...previousMcp, enabled: true },
|
|
120
120
|
},
|
|
121
121
|
},
|
|
122
122
|
};
|
|
@@ -124,14 +124,14 @@ function updatedInstalledPlugins(installed, pluginRoot, now) {
|
|
|
124
124
|
const plugins = [];
|
|
125
125
|
let replaced = false;
|
|
126
126
|
for (const plugin of installed.plugins) {
|
|
127
|
-
if (plugin.id !==
|
|
127
|
+
if (plugin.id !== pluginId) {
|
|
128
128
|
plugins.push(plugin);
|
|
129
129
|
} else if (!replaced) {
|
|
130
|
-
plugins.push(
|
|
130
|
+
plugins.push(updatedPlugin);
|
|
131
131
|
replaced = true;
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
-
if (!replaced) plugins.push(
|
|
134
|
+
if (!replaced) plugins.push(updatedPlugin);
|
|
135
135
|
return { ...installed, version: 1, plugins };
|
|
136
136
|
}
|
|
137
137
|
|
|
@@ -149,11 +149,19 @@ function writeInstalledPlugins(filePath, installed) {
|
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
function
|
|
153
|
-
|
|
152
|
+
function installManagedPlugin({
|
|
153
|
+
packageRoot,
|
|
154
|
+
blunDir,
|
|
155
|
+
now = new Date().toISOString(),
|
|
156
|
+
pluginId,
|
|
157
|
+
sourceDirectory,
|
|
158
|
+
mcpServerName,
|
|
159
|
+
displayName,
|
|
160
|
+
}) {
|
|
161
|
+
const sourceRoot = path.join(packageRoot, sourceDirectory);
|
|
154
162
|
const pluginsDir = path.join(blunDir, 'plugins');
|
|
155
163
|
const managedDir = path.join(pluginsDir, 'managed');
|
|
156
|
-
const legacyPluginRoot = path.join(managedDir,
|
|
164
|
+
const legacyPluginRoot = path.join(managedDir, pluginId);
|
|
157
165
|
const installedFile = path.join(pluginsDir, 'installed.json');
|
|
158
166
|
|
|
159
167
|
ensurePrivateDirectory(pluginsDir);
|
|
@@ -162,26 +170,38 @@ function installManagedTelegramPlugin({ packageRoot, blunDir, now = new Date().t
|
|
|
162
170
|
ensurePrivateDirectory(managedDir);
|
|
163
171
|
if (fs.existsSync(legacyPluginRoot)
|
|
164
172
|
&& pluginTreesAreIdentical(sourceRoot, legacyPluginRoot)) {
|
|
165
|
-
if (!
|
|
166
|
-
const updatedInstalled = updatedInstalledPlugins(
|
|
173
|
+
if (!hasCurrentPluginRecord(installed, legacyPluginRoot, pluginId, mcpServerName)) {
|
|
174
|
+
const updatedInstalled = updatedInstalledPlugins(
|
|
175
|
+
installed,
|
|
176
|
+
legacyPluginRoot,
|
|
177
|
+
now,
|
|
178
|
+
pluginId,
|
|
179
|
+
mcpServerName,
|
|
180
|
+
);
|
|
167
181
|
writeInstalledPlugins(installedFile, updatedInstalled);
|
|
168
182
|
}
|
|
169
183
|
return { installedFile, pluginRoot: legacyPluginRoot, reused: true };
|
|
170
184
|
}
|
|
171
185
|
const sourceDigest = pluginTreeDigest(sourceRoot);
|
|
172
|
-
const pluginRoot = path.join(managedDir,
|
|
173
|
-
const updatedInstalled = updatedInstalledPlugins(
|
|
186
|
+
const pluginRoot = path.join(managedDir, `${pluginId}-${sourceDigest}`);
|
|
187
|
+
const updatedInstalled = updatedInstalledPlugins(
|
|
188
|
+
installed,
|
|
189
|
+
pluginRoot,
|
|
190
|
+
now,
|
|
191
|
+
pluginId,
|
|
192
|
+
mcpServerName,
|
|
193
|
+
);
|
|
174
194
|
if (fs.existsSync(pluginRoot)) {
|
|
175
195
|
if (!pluginTreesAreIdentical(sourceRoot, pluginRoot)) {
|
|
176
|
-
throw new Error(`Managed
|
|
196
|
+
throw new Error(`Managed ${displayName} plugin digest mismatch: ${pluginRoot}`);
|
|
177
197
|
}
|
|
178
|
-
if (!
|
|
198
|
+
if (!hasCurrentPluginRecord(installed, pluginRoot, pluginId, mcpServerName)) {
|
|
179
199
|
writeInstalledPlugins(installedFile, updatedInstalled);
|
|
180
200
|
}
|
|
181
201
|
return { installedFile, pluginRoot, reused: true };
|
|
182
202
|
}
|
|
183
203
|
const nonce = `${process.pid}-${randomUUID()}`;
|
|
184
|
-
const stagingRoot = path.join(managedDir,
|
|
204
|
+
const stagingRoot = path.join(managedDir, `.${pluginId}-${nonce}.tmp`);
|
|
185
205
|
let installedNewPlugin = false;
|
|
186
206
|
try {
|
|
187
207
|
fs.cpSync(sourceRoot, stagingRoot, { recursive: true, force: true });
|
|
@@ -201,7 +221,28 @@ function installManagedTelegramPlugin({ packageRoot, blunDir, now = new Date().t
|
|
|
201
221
|
return { installedFile, pluginRoot, reused: false };
|
|
202
222
|
}
|
|
203
223
|
|
|
224
|
+
function installManagedTelegramPlugin(options) {
|
|
225
|
+
return installManagedPlugin({
|
|
226
|
+
...options,
|
|
227
|
+
pluginId: 'telegram',
|
|
228
|
+
sourceDirectory: 'telegram-plugin',
|
|
229
|
+
mcpServerName: 'telegram',
|
|
230
|
+
displayName: 'Telegram',
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function installManagedAgentSpinePlugin(options) {
|
|
235
|
+
return installManagedPlugin({
|
|
236
|
+
...options,
|
|
237
|
+
pluginId: 'agent-spine',
|
|
238
|
+
sourceDirectory: 'agent-spine-plugin',
|
|
239
|
+
mcpServerName: 'agent-spine',
|
|
240
|
+
displayName: 'AgentSpine',
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
204
244
|
module.exports = {
|
|
245
|
+
installManagedAgentSpinePlugin,
|
|
205
246
|
installManagedTelegramPlugin,
|
|
206
247
|
readInstalledPlugins,
|
|
207
248
|
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const CONFIG_FILE = 'tui.toml';
|
|
7
|
+
const MIGRATION_MARKER = '.legacy-auto-ui-locale-v1';
|
|
8
|
+
const MIGRATION_BACKUP = 'tui.toml.vor-english-default-v1';
|
|
9
|
+
const MAX_CONFIG_BYTES = 256 * 1024;
|
|
10
|
+
|
|
11
|
+
function safeConfigPath(profileHome, fsImpl = fs) {
|
|
12
|
+
const configPath = path.join(path.resolve(profileHome), CONFIG_FILE);
|
|
13
|
+
if (!fsImpl.existsSync(configPath)) return undefined;
|
|
14
|
+
const stat = fsImpl.lstatSync(configPath);
|
|
15
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_CONFIG_BYTES) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
return configPath;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function parseStartupPreferences(text) {
|
|
22
|
+
let section = '';
|
|
23
|
+
let autoInstall = true;
|
|
24
|
+
let uiLocale = 'en';
|
|
25
|
+
|
|
26
|
+
for (const line of String(text).replace(/^\uFEFF/u, '').split(/\r?\n/u)) {
|
|
27
|
+
const sectionMatch = /^\s*\[([^\]]+)\]\s*(?:#.*)?$/u.exec(line);
|
|
28
|
+
if (sectionMatch) {
|
|
29
|
+
section = sectionMatch[1].trim().toLowerCase();
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (section === '') {
|
|
33
|
+
const localeMatch = /^\s*ui_locale\s*=\s*"([^"]+)"\s*(?:#.*)?$/u.exec(line);
|
|
34
|
+
if (localeMatch && /^(?:auto|[a-z]{2}(?:-[a-z0-9]+)*)$/iu.test(localeMatch[1])) {
|
|
35
|
+
uiLocale = localeMatch[1].toLowerCase();
|
|
36
|
+
}
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (section === 'upgrade') {
|
|
40
|
+
const automaticMatch = /^\s*auto_install\s*=\s*(true|false)\s*(?:#.*)?$/iu.exec(line);
|
|
41
|
+
if (automaticMatch) autoInstall = automaticMatch[1].toLowerCase() === 'true';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { autoInstall, uiLocale };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readStartupPreferences(profileHome, fsImpl = fs) {
|
|
49
|
+
const configPath = safeConfigPath(profileHome, fsImpl);
|
|
50
|
+
if (!configPath) return { autoInstall: true, uiLocale: 'en' };
|
|
51
|
+
try {
|
|
52
|
+
return parseStartupPreferences(fsImpl.readFileSync(configPath, 'utf8'));
|
|
53
|
+
} catch {
|
|
54
|
+
return { autoInstall: true, uiLocale: 'en' };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolveStartupUiLocale(uiLocale, env = process.env) {
|
|
59
|
+
const normalized = String(uiLocale || 'en').trim().toLowerCase();
|
|
60
|
+
if (normalized !== 'auto') return normalized;
|
|
61
|
+
const operatingSystemLocale = String(env.LC_ALL || env.LC_MESSAGES || env.LANG || '')
|
|
62
|
+
.trim()
|
|
63
|
+
.toLowerCase();
|
|
64
|
+
return operatingSystemLocale === 'de'
|
|
65
|
+
|| operatingSystemLocale.startsWith('de-')
|
|
66
|
+
|| operatingSystemLocale.startsWith('de_')
|
|
67
|
+
|| operatingSystemLocale.startsWith('de.')
|
|
68
|
+
? 'de'
|
|
69
|
+
: 'en';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function writeMarkerOnce(markerPath, fsImpl) {
|
|
73
|
+
try {
|
|
74
|
+
fsImpl.writeFileSync(markerPath, 'legacy automatic UI locale reviewed once\n', {
|
|
75
|
+
encoding: 'utf8',
|
|
76
|
+
flag: 'wx',
|
|
77
|
+
});
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (error?.code !== 'EEXIST') throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function replaceIfUnchanged(target, original, replacement, fsImpl) {
|
|
84
|
+
if (fsImpl.readFileSync(target, 'utf8') !== original) {
|
|
85
|
+
throw new Error(`Startup preferences changed during migration: ${target}`);
|
|
86
|
+
}
|
|
87
|
+
const temporary = `${target}.migration-${process.pid}-${Date.now()}.tmp`;
|
|
88
|
+
fsImpl.writeFileSync(temporary, replacement, 'utf8');
|
|
89
|
+
try {
|
|
90
|
+
fsImpl.renameSync(temporary, target);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
try {
|
|
93
|
+
fsImpl.rmSync(temporary, { force: true });
|
|
94
|
+
} catch {}
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function migrateLegacyStartupPreferences(profileHome, fsImpl = fs) {
|
|
100
|
+
const resolvedHome = path.resolve(profileHome);
|
|
101
|
+
fsImpl.mkdirSync(resolvedHome, { recursive: true });
|
|
102
|
+
const markerPath = path.join(resolvedHome, MIGRATION_MARKER);
|
|
103
|
+
const backupPath = path.join(resolvedHome, MIGRATION_BACKUP);
|
|
104
|
+
if (fsImpl.existsSync(markerPath)) return { changed: false };
|
|
105
|
+
|
|
106
|
+
const configPath = safeConfigPath(resolvedHome, fsImpl);
|
|
107
|
+
if (!configPath) {
|
|
108
|
+
writeMarkerOnce(markerPath, fsImpl);
|
|
109
|
+
return { changed: false };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const original = fsImpl.readFileSync(configPath, 'utf8');
|
|
113
|
+
const legacyLocale = /^(\s*ui_locale\s*=\s*)"auto"(\s*(?:#.*)?)$/mu;
|
|
114
|
+
if (!legacyLocale.test(original)) {
|
|
115
|
+
writeMarkerOnce(markerPath, fsImpl);
|
|
116
|
+
return { changed: false };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!fsImpl.existsSync(backupPath)) fsImpl.copyFileSync(configPath, backupPath);
|
|
120
|
+
const replacement = original.replace(legacyLocale, '$1"en"$2');
|
|
121
|
+
replaceIfUnchanged(configPath, original, replacement, fsImpl);
|
|
122
|
+
writeMarkerOnce(markerPath, fsImpl);
|
|
123
|
+
return { backupPath, changed: true };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = {
|
|
127
|
+
migrateLegacyStartupPreferences,
|
|
128
|
+
readStartupPreferences,
|
|
129
|
+
resolveStartupUiLocale,
|
|
130
|
+
};
|
|
@@ -38,10 +38,11 @@ function shouldEnableThinkingForTurn(text, originKind) {
|
|
|
38
38
|
return selectThinkingEffortForTurn(text, originKind) !== "off";
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function selectThinkingEffortForWorkStep(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
function selectThinkingEffortForWorkStep(_stepNumber, _previousToolOutcome) {
|
|
42
|
+
// A successful tool call does not make the next agent decision routine.
|
|
43
|
+
// Preserve the turn-selected effort; short chat and lightweight steers are
|
|
44
|
+
// classified independently before the work loop starts.
|
|
45
|
+
return undefined;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
function capCompletionBudgetForAdaptiveEffort(budget, thinkingEffort) {
|