blun-king-cli 9.1.527 → 9.1.550

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.
Files changed (108) hide show
  1. package/LIESMICH.txt +13 -834
  2. package/README.md +41 -799
  3. package/bin/agent-resume-snapshot.cjs +31 -0
  4. package/bin/assistant-message-offload-policy.cjs +23 -2
  5. package/bin/codebase-search-runtime.cjs +23 -0
  6. package/bin/context-performance-policy.cjs +2 -5
  7. package/bin/context-pressure-policy.cjs +20 -0
  8. package/bin/cron-run-output.cjs +45 -0
  9. package/bin/cron-run-store.cjs +145 -0
  10. package/bin/durable-task-resume-policy.cjs +130 -0
  11. package/bin/durable-task-resume-runtime.cjs +117 -0
  12. package/bin/durable-task-resume-store.cjs +88 -0
  13. package/bin/editable-tool-approval-policy.cjs +540 -0
  14. package/bin/editable-tool-approval-runtime.cjs +99 -0
  15. package/bin/empty-response-retry-policy.cjs +29 -0
  16. package/bin/history-offload-pressure-policy.cjs +33 -0
  17. package/bin/html-to-research-markdown.cjs +146 -0
  18. package/bin/programmatic-context-isolation.cjs +25 -0
  19. package/bin/programmatic-tool-runtime.mjs +627 -0
  20. package/bin/read-continuation-policy.cjs +36 -5
  21. package/bin/scoped-cron-run-policy.cjs +358 -0
  22. package/bin/skill-activation-performance-policy.cjs +9 -0
  23. package/bin/startup-preferences.cjs +3 -3
  24. package/bin/structured-agent-swarm-output.cjs +325 -0
  25. package/bin/structured-subagent-output.cjs +252 -0
  26. package/bin/subagent-context-fork-policy.cjs +155 -0
  27. package/bin/subagent-skill-policy.cjs +204 -0
  28. package/bin/telegram-approval-relay.cjs +2 -1
  29. package/bin/telegram-direct-focus-policy.cjs +25 -1
  30. package/bin/todo-list-turn-policy.cjs +111 -1
  31. package/bin/tool-result-offload-policy.cjs +43 -1
  32. package/bin/turn-thinking-policy.cjs +6 -15
  33. package/bin/turn-tool-performance-policy.cjs +5 -4
  34. package/bin/update-notice.js +14 -18
  35. package/bin/user-message-offload-policy.cjs +12 -1
  36. package/blun.mjs +1406 -265
  37. package/codebase-index/README.md +82 -0
  38. package/codebase-index/codebase_index.py +469 -0
  39. package/package.json +24 -38
  40. package/telegram-plugin/bin/telegram-typing-keepalive.cjs +89 -0
  41. package/telegram-plugin/dist/bridge.mjs +8 -1
  42. package/CHANGELOG.md +0 -260
  43. package/agent-spine-plugin/CHANGELOG.md +0 -406
  44. package/agent-spine-plugin/CONTRIBUTING.md +0 -52
  45. package/agent-spine-plugin/README.md +0 -344
  46. package/agent-spine-plugin/SECURITY.md +0 -47
  47. package/agent-spine-plugin/docs/acceptance.md +0 -61
  48. package/agent-spine-plugin/docs/architecture.md +0 -183
  49. package/agent-spine-plugin/docs/attention.md +0 -121
  50. package/agent-spine-plugin/docs/automatic-continuity.md +0 -79
  51. package/agent-spine-plugin/docs/channel-runtime.md +0 -92
  52. package/agent-spine-plugin/docs/coordination.md +0 -138
  53. package/agent-spine-plugin/docs/feed-transport.md +0 -99
  54. package/agent-spine-plugin/docs/gateway-runtime.md +0 -116
  55. package/agent-spine-plugin/docs/harness-reference.md +0 -45
  56. package/agent-spine-plugin/docs/host-integration.md +0 -129
  57. package/agent-spine-plugin/docs/https-transport.md +0 -116
  58. package/agent-spine-plugin/docs/learning.md +0 -133
  59. package/agent-spine-plugin/docs/object-transport.md +0 -93
  60. package/agent-spine-plugin/docs/peer-transport.md +0 -88
  61. package/agent-spine-plugin/docs/preflight-recall.md +0 -69
  62. package/agent-spine-plugin/docs/preservation-contract.md +0 -53
  63. package/agent-spine-plugin/docs/quality-gates.md +0 -50
  64. package/agent-spine-plugin/docs/relationships.md +0 -73
  65. package/agent-spine-plugin/docs/releasing.md +0 -83
  66. package/agent-spine-plugin/docs/roadmap.md +0 -307
  67. package/agent-spine-plugin/docs/selfstarter.md +0 -88
  68. package/agent-spine-plugin/docs/session-briefing.md +0 -74
  69. package/agent-spine-plugin/docs/shared-memory.md +0 -259
  70. package/agent-spine-plugin/docs/source-roots.md +0 -86
  71. package/agent-spine-plugin/docs/sqlite-transport.md +0 -76
  72. package/agent-spine-plugin/scripts/check-hosts.js +0 -195
  73. package/agent-spine-plugin/scripts/check-install.js +0 -569
  74. package/agent-spine-plugin/scripts/check-syntax.js +0 -29
  75. package/agent-spine-plugin/scripts/github-actions.js +0 -11
  76. package/agent-spine-plugin/scripts/release-check.js +0 -128
  77. package/agent-spine-plugin/scripts/run-acceptance.js +0 -19
  78. package/agent-spine-plugin/scripts/run-checks.js +0 -46
  79. package/agent-spine-plugin/scripts/run-tests-hermetic.js +0 -73
  80. package/agent-spine-plugin/spine-example/1-identity.md +0 -12
  81. package/agent-spine-plugin/spine-example/2-voice.md +0 -6
  82. package/agent-spine-plugin/spine-example/3-conduct.md +0 -8
  83. package/agent-spine-plugin/spine-example/4-history.md +0 -4
  84. package/bin/package-regression-policy.cjs +0 -77
  85. package/release-planned-removals.json +0 -15
  86. package/scripts/check-active-profile-plugin-startup.js +0 -36
  87. package/scripts/check-approval-observability-regression.js +0 -111
  88. package/scripts/check-approval-queue-shortcuts-regression.js +0 -65
  89. package/scripts/check-bundled-agent-spine-regression.js +0 -48
  90. package/scripts/check-copy-command-regression.js +0 -74
  91. package/scripts/check-historical-tool-result-preview-regression.js +0 -77
  92. package/scripts/check-mcp-startup-wait-budget.js +0 -48
  93. package/scripts/check-package-regression.js +0 -38
  94. package/scripts/check-plugin-startup-regression.js +0 -53
  95. package/scripts/check-queue-controls-regression.js +0 -189
  96. package/scripts/check-release-metadata.js +0 -103
  97. package/scripts/check-reload-agent-spine-regression.js +0 -76
  98. package/scripts/check-resume-replay-regression.js +0 -100
  99. package/scripts/check-session-cancel-regression.js +0 -43
  100. package/scripts/check-session-picker-resume-metrics-regression.js +0 -97
  101. package/scripts/check-session-start-hook-context-regression.js +0 -228
  102. package/scripts/check-shell-terminal-isolation-regression.js +0 -81
  103. package/scripts/check-slash-escape-regression.js +0 -89
  104. package/scripts/check-telegram-bridge-watchdog.js +0 -60
  105. package/scripts/check-telegram-loop-exactly-once-regression.js +0 -71
  106. package/scripts/check-todo-loop-regression.js +0 -78
  107. package/scripts/check-todo-recovery-catalog-regression.js +0 -50
  108. /package/{scripts → bin}/fix-node-pty-perms.js +0 -0
@@ -0,0 +1,155 @@
1
+ 'use strict';
2
+
3
+ const DEFAULT_MAX_MESSAGES = 16;
4
+ const DEFAULT_MAX_CHARS = 64 * 1024;
5
+ const MAX_CONFIGURED_MESSAGES = 64;
6
+ const MAX_CONFIGURED_CHARS = 256 * 1024;
7
+
8
+ const SUBAGENT_FORK_REMINDER = [
9
+ 'You received a bounded, redacted projection of the parent conversation.',
10
+ 'It contains only direct local user text and completed text-only assistant replies.',
11
+ 'It excludes system injections, private channel messages, attachments, tool calls, tool arguments, and tool results.',
12
+ 'Treat that history as already happened. Your actual delegated task below is authoritative.',
13
+ 'Do not launch another context fork. Return the requested result to the parent agent.',
14
+ ].join(' ');
15
+
16
+ function boundedPositiveInteger(value, fallback, maximum, name) {
17
+ if (value === undefined) return fallback;
18
+ if (!Number.isSafeInteger(value) || value <= 0 || value > maximum) {
19
+ throw new TypeError(`${name} must be an integer between 1 and ${String(maximum)}`);
20
+ }
21
+ return value;
22
+ }
23
+
24
+ function resolveSubagentContextMode(options = {}) {
25
+ const requested = options.requested ?? 'isolated';
26
+ if (requested !== 'isolated' && requested !== 'fork') {
27
+ throw new TypeError('context_mode must be either "isolated" or "fork"');
28
+ }
29
+ if (requested === 'isolated') return requested;
30
+ if (options.resume === true) {
31
+ throw new Error('context_mode="fork" is available only when creating a new subagent, not while resuming one');
32
+ }
33
+ if (options.isRoot !== true) {
34
+ throw new Error('context_mode="fork" is available only to the root agent');
35
+ }
36
+ return requested;
37
+ }
38
+
39
+ function redactSecrets(value) {
40
+ return String(value)
41
+ .replaceAll(/-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/giu, '[redacted]')
42
+ .replaceAll(/\b(authorization)\s*:\s*(?:basic|bearer)\s+\S+/giu, '$1: [redacted]')
43
+ .replaceAll(/\b(api[_ -]?(?:key|token)|access[_ -]?token|refresh[_ -]?token|id[_ -]?token|token|secret|password|passwd|pwd|credential)\b\s*([:=])\s*(?:"[^"]*"|'[^']*'|\S+)/giu, '$1$2[redacted]')
44
+ .replaceAll(/\b(?:sk|ghp|xoxb|xoxp|token)[-_][A-Za-z0-9_-]{16,}\b/gu, '[redacted]')
45
+ .replaceAll(/\b[A-Za-z0-9][A-Za-z0-9+/=_-]{39,}\b/gu, '[redacted]');
46
+ }
47
+
48
+ function textContent(message) {
49
+ if (!Array.isArray(message?.content)) return '';
50
+ return message.content
51
+ .filter((part) => part?.type === 'text' && typeof part.text === 'string')
52
+ .map((part) => part.text)
53
+ .join('\n')
54
+ .trim();
55
+ }
56
+
57
+ function eligibleMessage(message) {
58
+ if (message?.role === 'user') {
59
+ if (message.origin?.kind !== 'user') return undefined;
60
+ } else if (message?.role === 'assistant') {
61
+ if (message.origin !== undefined) return undefined;
62
+ if (!Array.isArray(message.toolCalls) || message.toolCalls.length > 0) return undefined;
63
+ } else {
64
+ return undefined;
65
+ }
66
+ const text = textContent(message);
67
+ if (text.length === 0) return undefined;
68
+ return { role: message.role, text: redactSecrets(text) };
69
+ }
70
+
71
+ function modelMessage(role, text) {
72
+ return {
73
+ role,
74
+ content: [{ type: 'text', text }],
75
+ toolCalls: [],
76
+ };
77
+ }
78
+
79
+ function mergeAdjacent(messages) {
80
+ const merged = [];
81
+ for (const message of messages) {
82
+ const previous = merged.at(-1);
83
+ if (previous?.role === message.role) {
84
+ previous.content[0].text += `\n\n${message.content[0].text}`;
85
+ } else {
86
+ merged.push(modelMessage(message.role, message.content[0].text));
87
+ }
88
+ }
89
+ while (merged[0]?.role === 'assistant') merged.shift();
90
+ return merged;
91
+ }
92
+
93
+ function emptyProjection() {
94
+ return {
95
+ messages: [],
96
+ sourceMessageCount: 0,
97
+ sourceChars: 0,
98
+ truncatedOlderContext: false,
99
+ };
100
+ }
101
+
102
+ function projectSubagentForkContext(history, options = {}) {
103
+ if (!Array.isArray(history)) throw new TypeError('history must be an array');
104
+ const maxMessages = boundedPositiveInteger(
105
+ options.maxMessages,
106
+ DEFAULT_MAX_MESSAGES,
107
+ MAX_CONFIGURED_MESSAGES,
108
+ 'maxMessages',
109
+ );
110
+ const maxChars = boundedPositiveInteger(
111
+ options.maxChars,
112
+ DEFAULT_MAX_CHARS,
113
+ MAX_CONFIGURED_CHARS,
114
+ 'maxChars',
115
+ );
116
+ const eligible = history.map(eligibleMessage).filter((message) => message !== undefined);
117
+ const newestUserIndex = eligible.findLastIndex((message) => message.role === 'user');
118
+ if (newestUserIndex < 0) return emptyProjection();
119
+ const throughCurrentUser = eligible.slice(0, newestUserIndex + 1);
120
+ const newestUser = throughCurrentUser.at(-1);
121
+ if (newestUser.text.length > maxChars) {
122
+ throw new Error(`newest direct user message exceeds the ${String(maxChars)}-character fork context limit`);
123
+ }
124
+
125
+ const selected = [];
126
+ let sourceChars = 0;
127
+ let truncatedOlderContext = false;
128
+ for (let index = throughCurrentUser.length - 1; index >= 0; index -= 1) {
129
+ const message = throughCurrentUser[index];
130
+ if (selected.length >= maxMessages || sourceChars + message.text.length > maxChars) {
131
+ truncatedOlderContext = true;
132
+ break;
133
+ }
134
+ selected.push(modelMessage(message.role, message.text));
135
+ sourceChars += message.text.length;
136
+ }
137
+ selected.reverse();
138
+ const messages = mergeAdjacent(selected);
139
+ const retainedMessageCount = selected.length - selected.findIndex((message) => message.role === 'user');
140
+ return {
141
+ messages,
142
+ sourceMessageCount: retainedMessageCount,
143
+ sourceChars: messages.reduce((total, message) => total + message.content[0].text.replaceAll('\n\n', '').length, 0),
144
+ truncatedOlderContext,
145
+ };
146
+ }
147
+
148
+ module.exports = {
149
+ DEFAULT_MAX_CHARS,
150
+ DEFAULT_MAX_MESSAGES,
151
+ SUBAGENT_FORK_REMINDER,
152
+ projectSubagentForkContext,
153
+ redactSecrets,
154
+ resolveSubagentContextMode,
155
+ };
@@ -0,0 +1,204 @@
1
+ 'use strict';
2
+
3
+ const { createHash } = require('node:crypto');
4
+
5
+ const POLICY_VERSION = 1;
6
+ const MAX_SKILLS = 32;
7
+ const MAX_NAME_BYTES = 160;
8
+ const INVALID_CODE = 'SUBAGENT_SKILL_POLICY_INVALID';
9
+ const CHANGED_CODE = 'SUBAGENT_SKILL_POLICY_CHANGED';
10
+
11
+ class SubagentSkillPolicyError extends Error {
12
+ constructor(code, message) {
13
+ super(`${code}: ${message}`);
14
+ this.name = 'SubagentSkillPolicyError';
15
+ this.code = code;
16
+ }
17
+ }
18
+
19
+ function normalizeName(name) {
20
+ return name.toLowerCase();
21
+ }
22
+
23
+ function sha256(value) {
24
+ return `sha256:${createHash('sha256').update(value).digest('hex')}`;
25
+ }
26
+
27
+ function digestJson(value) {
28
+ return sha256(JSON.stringify(value));
29
+ }
30
+
31
+ function isInlineSkill(skill) {
32
+ const type = skill?.metadata?.type;
33
+ return type === undefined || type === 'prompt' || type === 'inline';
34
+ }
35
+
36
+ function frozenSkill(skill) {
37
+ const metadata = Object.freeze({ ...(skill.metadata || {}) });
38
+ const plugin = skill.plugin === undefined
39
+ ? undefined
40
+ : Object.freeze({ ...skill.plugin });
41
+ return Object.freeze({ ...skill, metadata, ...(plugin === undefined ? {} : { plugin }) });
42
+ }
43
+
44
+ function catalogEntry(skill) {
45
+ return {
46
+ name: normalizeName(skill.name),
47
+ source: skill.source ?? null,
48
+ path: skill.path ?? null,
49
+ metadata: {
50
+ type: skill.metadata.type ?? null,
51
+ disableModelInvocation: skill.metadata.disableModelInvocation === true,
52
+ isSubSkill: skill.metadata.isSubSkill === true,
53
+ },
54
+ plugin: skill.plugin === undefined
55
+ ? null
56
+ : {
57
+ id: skill.plugin.id ?? null,
58
+ instructionsSha256: sha256(skill.plugin.instructions ?? ''),
59
+ },
60
+ contentSha256: sha256(skill.content ?? ''),
61
+ };
62
+ }
63
+
64
+ function defaultModelListing(skills) {
65
+ if (skills.length === 0) return '';
66
+ return [
67
+ 'Only the profile-allowed skills are available to this subagent.',
68
+ ...skills.map((skill) => `- ${skill.name}: ${skill.description ?? ''}`.trimEnd()),
69
+ ].join('\n');
70
+ }
71
+
72
+ function createRegistryView(selected, sourceRegistry, options) {
73
+ const ordered = Object.freeze([...selected].sort((a, b) => a.name.localeCompare(b.name)));
74
+ const byName = new Map(ordered.map((skill) => [normalizeName(skill.name), skill]));
75
+ const renderModelListing = options.renderModelListing ?? defaultModelListing;
76
+
77
+ return Object.freeze({
78
+ getSkill(name) {
79
+ return typeof name === 'string' ? byName.get(normalizeName(name)) : undefined;
80
+ },
81
+ listSkills() {
82
+ return [...ordered];
83
+ },
84
+ listInvocableSkills() {
85
+ return [...ordered];
86
+ },
87
+ getModelSkillListing() {
88
+ return renderModelListing([...ordered]);
89
+ },
90
+ renderSkillPrompt(skill, args) {
91
+ const expected = skill && typeof skill.name === 'string'
92
+ ? byName.get(normalizeName(skill.name))
93
+ : undefined;
94
+ if (expected === undefined || expected !== skill) {
95
+ throw new SubagentSkillPolicyError(INVALID_CODE, 'skill object is outside the profile allowlist');
96
+ }
97
+ return sourceRegistry.renderSkillPrompt(skill, args);
98
+ },
99
+ });
100
+ }
101
+
102
+ function validateNames(rawNames) {
103
+ if (rawNames === undefined) return [];
104
+ if (!Array.isArray(rawNames)) {
105
+ throw new SubagentSkillPolicyError(INVALID_CODE, 'skills must be an array');
106
+ }
107
+ if (rawNames.length > MAX_SKILLS) {
108
+ throw new SubagentSkillPolicyError(INVALID_CODE, `skills exceeds the ${MAX_SKILLS}-entry limit`);
109
+ }
110
+ const seen = new Set();
111
+ return rawNames.map((name) => {
112
+ if (typeof name !== 'string' || name.trim().length === 0) {
113
+ throw new SubagentSkillPolicyError(INVALID_CODE, 'skill names must be non-empty strings');
114
+ }
115
+ if (Buffer.byteLength(name, 'utf8') > MAX_NAME_BYTES) {
116
+ throw new SubagentSkillPolicyError(INVALID_CODE, `skill name exceeds ${MAX_NAME_BYTES} UTF-8 bytes`);
117
+ }
118
+ const normalized = normalizeName(name);
119
+ if (seen.has(normalized)) {
120
+ throw new SubagentSkillPolicyError(INVALID_CODE, `duplicate skill name "${name}"`);
121
+ }
122
+ seen.add(normalized);
123
+ return { requested: name, normalized };
124
+ });
125
+ }
126
+
127
+ function prepareSubagentSkillPolicy(rawNames, sourceRegistry, options = {}) {
128
+ if (sourceRegistry === undefined || sourceRegistry === null) {
129
+ throw new SubagentSkillPolicyError(INVALID_CODE, 'session skill registry is unavailable');
130
+ }
131
+ const requested = validateNames(rawNames);
132
+ const selected = requested.map(({ requested: name, normalized }) => {
133
+ const skill = sourceRegistry.getSkill(name);
134
+ if (skill === undefined || normalizeName(skill.name) !== normalized) {
135
+ throw new SubagentSkillPolicyError(INVALID_CODE, `skill "${name}" is not registered`);
136
+ }
137
+ if (skill.metadata?.disableModelInvocation === true) {
138
+ throw new SubagentSkillPolicyError(INVALID_CODE, `skill "${name}" disables model invocation`);
139
+ }
140
+ if (!isInlineSkill(skill)) {
141
+ throw new SubagentSkillPolicyError(INVALID_CODE, `skill "${name}" is not inline`);
142
+ }
143
+ return frozenSkill(skill);
144
+ });
145
+ const normalizedNames = Object.freeze(requested.map((entry) => entry.normalized));
146
+ const metadata = Object.freeze({
147
+ version: POLICY_VERSION,
148
+ names: normalizedNames,
149
+ profileDigest: digestJson(normalizedNames),
150
+ catalogDigest: digestJson(selected.map(catalogEntry)),
151
+ });
152
+ const enabled = selected.length > 0;
153
+ return Object.freeze({
154
+ enabled,
155
+ registry: enabled ? createRegistryView(selected, sourceRegistry, options) : null,
156
+ metadata,
157
+ });
158
+ }
159
+
160
+ function validPersistedPolicy(policy) {
161
+ return policy !== null
162
+ && typeof policy === 'object'
163
+ && policy.version === POLICY_VERSION
164
+ && Array.isArray(policy.names)
165
+ && typeof policy.profileDigest === 'string'
166
+ && typeof policy.catalogDigest === 'string';
167
+ }
168
+
169
+ function restoreSubagentSkillPolicy(rawNames, persistedPolicy, sourceRegistry, options = {}) {
170
+ if (!validPersistedPolicy(persistedPolicy)) {
171
+ throw new SubagentSkillPolicyError(CHANGED_CODE, 'persisted skill policy is missing or invalid');
172
+ }
173
+ let prepared;
174
+ try {
175
+ prepared = prepareSubagentSkillPolicy(rawNames, sourceRegistry, options);
176
+ } catch (error) {
177
+ throw new SubagentSkillPolicyError(CHANGED_CODE, error instanceof Error ? error.message : String(error));
178
+ }
179
+ if (JSON.stringify(prepared.metadata.names) !== JSON.stringify(persistedPolicy.names)
180
+ || prepared.metadata.profileDigest !== persistedPolicy.profileDigest
181
+ || prepared.metadata.catalogDigest !== persistedPolicy.catalogDigest) {
182
+ throw new SubagentSkillPolicyError(CHANGED_CODE, 'profile or selected skill catalog changed; spawn a new subagent');
183
+ }
184
+ return prepared;
185
+ }
186
+
187
+ function profileWithSubagentSkillTools(profile, policy) {
188
+ const withoutSkill = profile.tools.filter((name) => name !== 'Skill');
189
+ const tools = policy.enabled ? [...withoutSkill, 'Skill'] : withoutSkill;
190
+ return Object.freeze({ ...profile, tools: Object.freeze(tools) });
191
+ }
192
+
193
+ module.exports = {
194
+ CHANGED_CODE,
195
+ INVALID_CODE,
196
+ MAX_NAME_BYTES,
197
+ MAX_SKILLS,
198
+ POLICY_VERSION,
199
+ SubagentSkillPolicyError,
200
+ prepareSubagentSkillPolicy,
201
+ profileWithSubagentSkillTools,
202
+ restoreSubagentSkillPolicy,
203
+ };
204
+
@@ -168,11 +168,12 @@ function beginTelegramApprovalRelay(payload, options = {}) {
168
168
  if (settled) return false;
169
169
  settled = true;
170
170
  if (interval !== undefined) clearInterval(interval);
171
+ const persistedResponse = response?.decision === 'edited' ? { decision: 'edited' } : response;
171
172
  atomicWriteJson(resolvedFile, {
172
173
  v: RELAY_VERSION,
173
174
  requestId: request.requestId,
174
175
  resolvedAt: Date.now(),
175
- response,
176
+ response: persistedResponse,
176
177
  });
177
178
  resolvePromise(response);
178
179
  return true;
@@ -10,6 +10,9 @@ const CONVERSATION_CLOSE = /^(?:danke(?: dir)?|dankesch(?:oe|\u00f6)n|alles klar
10
10
  const EXPLICIT_PAUSE = /^(?:(?:bitte\s+)?(?:pause|pausier(?:e)?|stop|stopp|warte|halt(?:e)?\s+an|nicht\s+weiter(?:machen)?))(?:[\s.!?,].*)?$/iu;
11
11
  const DIRECT_TELEGRAM_CHANNEL = /<channel\b(?=[^>]*\bsource=["']telegram["'])(?=[^>]*\bpriority=["']direct["'])[^>]*>/iu;
12
12
  const TELEGRAM_REPLY_TOOL = /^mcp__[^\s]*telegram[^\s]*__reply$/iu;
13
+ const DIRECT_CHANNEL_PAYLOAD = /<channel\b(?=[^>]*\bsource=["']telegram["'])(?=[^>]*\bpriority=["']direct["'])[^>]*>([\s\S]*?)<\/channel>/giu;
14
+ const DIRECT_WORK_INTENT = /\b(?:auftrag|arbeite|bau(?:e|en)?|build|check|continue|edit|feuer\s+frei|fix|fortsetzen|implement|install|lies|mach(?:e)?\s+weiter|mess|patch|pruef|resume|run|schreib|starte?|test|update|weiterarbeiten|weiterbauen|write)\b/iu;
15
+ const DIRECT_WORK_PAUSE = /\b(?:halt(?:e)?\s+an|nicht\s+weiter(?:machen)?|pause|pausier(?:e)?|stop|stopp|warte)\b/iu;
13
16
 
14
17
  function isPrivateTelegramChat(chatId) {
15
18
  return /^[1-9]\d*$/u.test(String(chatId ?? '').trim());
@@ -55,8 +58,26 @@ function isExplicitPauseRequest(text) {
55
58
  return EXPLICIT_PAUSE.test(String(text ?? '').trim());
56
59
  }
57
60
 
61
+ function directTelegramPayloadText(inputText) {
62
+ const matches = [...String(inputText ?? '').matchAll(DIRECT_CHANNEL_PAYLOAD)];
63
+ return String(matches.at(-1)?.[1] ?? '').trim();
64
+ }
65
+
66
+ function directMessageAllowsWorkContinuation(inputText) {
67
+ if (!DIRECT_TELEGRAM_CHANNEL.test(String(inputText ?? ''))) return false;
68
+ const payload = directTelegramPayloadText(inputText);
69
+ return payload.length > 0 && !DIRECT_WORK_PAUSE.test(payload);
70
+ }
71
+
72
+ function directMessageStartsOrResumesWork(inputText) {
73
+ if (!directMessageAllowsWorkContinuation(inputText)) return false;
74
+ const payload = directTelegramPayloadText(inputText);
75
+ return isResumeApproval(payload) || DIRECT_WORK_INTENT.test(payload);
76
+ }
77
+
58
78
  function createDirectReplyTurnStop(inputText) {
59
- const direct = DIRECT_TELEGRAM_CHANNEL.test(String(inputText ?? ''));
79
+ const direct = DIRECT_TELEGRAM_CHANNEL.test(String(inputText ?? ''))
80
+ && !directMessageStartsOrResumesWork(inputText);
60
81
  let delivered = false;
61
82
  return {
62
83
  noteToolResult(toolName, isError) {
@@ -236,6 +257,9 @@ module.exports = {
236
257
  DEFAULT_SILENCE_MS,
237
258
  createDirectFocusController,
238
259
  createDirectReplyTurnStop,
260
+ directMessageAllowsWorkContinuation,
261
+ directMessageStartsOrResumesWork,
262
+ directTelegramPayloadText,
239
263
  directFocusCheckpointPath,
240
264
  enqueueTelegramDirect,
241
265
  isConversationClose,
@@ -1,6 +1,109 @@
1
1
  'use strict';
2
2
 
3
3
  const TODO_LIST_TOOL_NAME = 'TodoList';
4
+ const MAX_TODO_ITEMS = 32;
5
+ const BASE_TODO_REFRESH_WORK_CALL_LIMIT = 8;
6
+ const MAX_TODO_REFRESH_WORK_CALL_LIMIT = 64;
7
+ const TODO_STALLED_WARNING_STREAK = 3;
8
+
9
+ function canonicalStoredTodos(todos) {
10
+ if (!Array.isArray(todos)) return [];
11
+ return todos
12
+ .filter((todo) => todo?.status !== 'done')
13
+ .map((todo) => ({
14
+ title: String(todo?.title ?? ''),
15
+ status: String(todo?.status ?? ''),
16
+ }));
17
+ }
18
+
19
+ function semanticTodoFingerprint(todos) {
20
+ return JSON.stringify(canonicalStoredTodos(todos)
21
+ .map((todo) => `${todo.status}\0${todo.title}`)
22
+ .sort());
23
+ }
24
+
25
+ function todoTitleCounts(todos) {
26
+ const counts = new Map();
27
+ for (const todo of Array.isArray(todos) ? todos : []) {
28
+ const title = String(todo?.title ?? '');
29
+ counts.set(title, (counts.get(title) ?? 0) + 1);
30
+ }
31
+ return counts;
32
+ }
33
+
34
+ function sameTitleCounts(left, right) {
35
+ if (left.size !== right.size) return false;
36
+ for (const [title, count] of left) {
37
+ if (right.get(title) !== count) return false;
38
+ }
39
+ return true;
40
+ }
41
+
42
+ function assessTodoListBounds(input) {
43
+ const currentTodos = canonicalStoredTodos(input?.currentTodos);
44
+ const nextTodos = canonicalStoredTodos(input?.nextTodos);
45
+ if (currentTodos.length > MAX_TODO_ITEMS) {
46
+ const currentTitles = todoTitleCounts(currentTodos);
47
+ const submittedTitles = todoTitleCounts(input?.nextTodos);
48
+ if (!sameTitleCounts(currentTitles, submittedTitles)) {
49
+ return {
50
+ block: true,
51
+ reason: `An oversized legacy TodoList may remove an unresolved legacy item only by submitting it as done. Do not add, omit, or rename items until the visible list is at or below ${String(MAX_TODO_ITEMS)}.`,
52
+ };
53
+ }
54
+
55
+ if (nextTodos.length > currentTodos.length) {
56
+ return {
57
+ block: true,
58
+ reason: `An oversized legacy TodoList must stay unchanged or shrink toward ${String(MAX_TODO_ITEMS)} visible items.`,
59
+ };
60
+ }
61
+
62
+ return undefined;
63
+ }
64
+
65
+ if (nextTodos.length <= MAX_TODO_ITEMS) return undefined;
66
+ return {
67
+ block: true,
68
+ reason: `TodoList accepts at most ${String(MAX_TODO_ITEMS)} visible items. Group the work into task-specific phases before continuing.`,
69
+ };
70
+ }
71
+
72
+ function todoRefreshWorkCallLimit(unchangedRefreshStreak) {
73
+ const exponent = Math.min(Math.max(Number(unchangedRefreshStreak) || 0, 0), 3);
74
+ return Math.min(
75
+ MAX_TODO_REFRESH_WORK_CALL_LIMIT,
76
+ BASE_TODO_REFRESH_WORK_CALL_LIMIT * (2 ** exponent),
77
+ );
78
+ }
79
+
80
+ function assessTodoMaintenanceUpdate(input) {
81
+ const changed = semanticTodoFingerprint(input?.currentTodos)
82
+ !== semanticTodoFingerprint(input?.nextTodos);
83
+ if (changed) {
84
+ return {
85
+ changed: true,
86
+ unchangedRefreshStreak: 0,
87
+ refreshWorkCallLimit: BASE_TODO_REFRESH_WORK_CALL_LIMIT,
88
+ preserveRecentEvidence: false,
89
+ requireConcreteProgress: false,
90
+ emitStalledWarning: false,
91
+ };
92
+ }
93
+
94
+ const previousStreak = Math.max(Number(input?.unchangedRefreshStreak) || 0, 0);
95
+ const hasNewEvidence = (Number(input?.recentEvidenceCount) || 0) > 0;
96
+ const unchangedRefreshStreak = hasNewEvidence ? previousStreak + 1 : previousStreak;
97
+ return {
98
+ changed: false,
99
+ unchangedRefreshStreak,
100
+ refreshWorkCallLimit: todoRefreshWorkCallLimit(unchangedRefreshStreak),
101
+ preserveRecentEvidence: true,
102
+ requireConcreteProgress: unchangedRefreshStreak >= 2,
103
+ emitStalledWarning: hasNewEvidence
104
+ && unchangedRefreshStreak === TODO_STALLED_WARNING_STREAK,
105
+ };
106
+ }
4
107
 
5
108
  function enforceSingleTodoWritePerStep(context) {
6
109
  if (context?.toolCall?.name !== TODO_LIST_TOOL_NAME) return undefined;
@@ -18,4 +121,11 @@ function enforceSingleTodoWritePerStep(context) {
18
121
  };
19
122
  }
20
123
 
21
- module.exports = { enforceSingleTodoWritePerStep };
124
+ module.exports = {
125
+ MAX_TODO_ITEMS,
126
+ assessTodoListBounds,
127
+ assessTodoMaintenanceUpdate,
128
+ canonicalStoredTodos,
129
+ enforceSingleTodoWritePerStep,
130
+ todoRefreshWorkCallLimit,
131
+ };
@@ -1,5 +1,10 @@
1
1
  'use strict';
2
2
 
3
+ const {
4
+ DEFAULT_CONTEXT_PRESSURE_RATIO,
5
+ hasContextPressure,
6
+ } = require('./context-pressure-policy.cjs');
7
+
3
8
  const TOOL_RESULT_MAX_CHARS = 12_000;
4
9
  const TOOL_RESULT_PREVIEW_CHARS = 2_000;
5
10
  const TOOL_RESULT_HISTORICAL_PREVIEW_CHARS = 400;
@@ -16,8 +21,13 @@ const TOOL_RESULT_HISTORICAL_REPLACEMENT_BUDGET_CHARS = 1_000;
16
21
  const TOOL_RESULT_SUCCESS_KEEP_RECENT_MESSAGES = 12;
17
22
  const TOOL_RESULT_HISTORICAL_SUCCESS_MARKER = '[Old tool result content cleared]';
18
23
  const TOOL_RESULT_REPEAT_MARKER = '[Repeated tool result omitted]';
24
+ const TOOL_RESULT_OFFLOAD_PRESSURE_RATIO = DEFAULT_CONTEXT_PRESSURE_RATIO;
19
25
  const READ_CONTINUATION_LINE_OFFSET_RE = /^Continue reading with line_offset=(\d+)\. Do not assume you have reached the end of the file\.$/mu;
20
26
 
27
+ function hasToolResultOffloadPressure(options = {}) {
28
+ return hasContextPressure(options);
29
+ }
30
+
21
31
  function shouldOffloadToolResult(textLength) {
22
32
  return Number.isFinite(textLength) && textLength > TOOL_RESULT_MAX_CHARS;
23
33
  }
@@ -78,6 +88,33 @@ function freshToolResultIds(messages) {
78
88
  return resultIds;
79
89
  }
80
90
 
91
+ function currentUserTurnStartIndex(messages) {
92
+ if (!Array.isArray(messages) || messages.length === 0) return -1;
93
+
94
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
95
+ const message = messages[index];
96
+ if (message?.role === 'user' && message.origin?.kind === 'user') return index;
97
+ }
98
+ return -1;
99
+ }
100
+
101
+ function currentTurnReadToolResultIds(messages) {
102
+ const turnStart = currentUserTurnStartIndex(messages);
103
+ if (turnStart < 0) return new Set();
104
+
105
+ const resultIds = new Set();
106
+ for (let index = turnStart + 1; index < messages.length; index += 1) {
107
+ const message = messages[index];
108
+ if (message?.role !== 'assistant' || !Array.isArray(message.toolCalls)) continue;
109
+ for (const call of message.toolCalls) {
110
+ if (call?.name !== 'Read') continue;
111
+ const id = call.id ?? call.toolCallId;
112
+ if (typeof id === 'string' && id.length > 0) resultIds.add(id);
113
+ }
114
+ }
115
+ return resultIds;
116
+ }
117
+
81
118
  function compactPersistedToolResultReference(content) {
82
119
  if (!isPersistedToolResultReference(content)) return content;
83
120
 
@@ -127,8 +164,9 @@ function compactPersistedToolResultReference(content) {
127
164
  return changed ? compacted : content;
128
165
  }
129
166
 
130
- function compactHistoricalSuccessfulToolResults(messages) {
167
+ function compactHistoricalSuccessfulToolResults(messages, options = {}) {
131
168
  if (!Array.isArray(messages) || messages.length === 0) return messages;
169
+ if (!hasToolResultOffloadPressure(options)) return messages;
132
170
 
133
171
  const recentStart = Math.max(0, messages.length - TOOL_RESULT_SUCCESS_KEEP_RECENT_MESSAGES);
134
172
  const freshIds = freshToolResultIds(messages);
@@ -299,6 +337,7 @@ function selectHistoricalToolResultOffloads(textLengths) {
299
337
  }
300
338
 
301
339
  module.exports = {
340
+ TOOL_RESULT_OFFLOAD_PRESSURE_RATIO,
302
341
  TOOL_RESULT_BATCH_MAX_CHARS,
303
342
  TOOL_RESULT_BATCH_MIN_ITEM_CHARS,
304
343
  TOOL_RESULT_BATCH_REPLACEMENT_BUDGET_CHARS,
@@ -318,8 +357,11 @@ module.exports = {
318
357
  compactHistoricalSuccessfulToolResults,
319
358
  compactPersistedToolResultReference,
320
359
  createToolResultPreview,
360
+ currentTurnReadToolResultIds,
361
+ currentUserTurnStartIndex,
321
362
  dedupeRepeatedSuccessfulToolResults,
322
363
  freshToolResultIds,
364
+ hasToolResultOffloadPressure,
323
365
  isPersistedToolResultReference,
324
366
  readContinuationLineOffset,
325
367
  selectHistoricalToolResultOffloads,
@@ -7,7 +7,6 @@ const BOSNIAN_STANDALONE_CHAT = /^(?:zdravo|pozdrav|hvala|ćao|cao)[.!?]*$/iu;
7
7
  const IDENTITY_CHAT = /^(?:wer bist du|who are you|wie geht(?: es dir)?|how are you|na|kako si|kako je king|ko si ti|ko stoji iza tebe)[.!?]*$/iu;
8
8
  const STATUS_ONLY_CHECK_IN = /(?:\bstandabfrage\b|\bstatusabfrage\b|\bkurzes?\s+update\b|\bwo\s+stehst\s+du\b|\bwie\s+weit\s+bist\s+du\b|\bwhere\s+are\s+you\b|\bquick\s+status\b)/iu;
9
9
  const STATUS_FOLLOW_UP_ACTION = /\b(?:aendere|arbeite|baue|build|change|delete|deploy|deploye|fahr|fix|fixe|implement|implementiere|install|installiere|loesche|mach|patch|pruefe|repariere|run|schreibe|setze|start|starte|stop|stoppe|test|teste|write)\b/iu;
10
- const LIGHTWEIGHT_STEER = /\b(?:cool|danke|dankeschoen|gute arbeit|passt|perfekt|stolz|super|freut mich)\b/iu;
11
10
  const STANDALONE_CHAT_COMPLETION_TOKENS = 1024;
12
11
  const ADAPTIVE_LOW_COMPLETION_TOKENS = 4096;
13
12
  const STANDARD_TURN_MAX_COMPLETION_TOKENS = 24_576;
@@ -68,20 +67,12 @@ function capTurnCompletionTokens(maxOutputSize) {
68
67
  }
69
68
 
70
69
  function selectThinkingEffortForBufferedSteer(text, originKind) {
71
- const classified = selectThinkingEffortForTurn(text, originKind);
72
- if (classified !== undefined) return classified;
73
- if (originKind !== "user") return undefined;
74
-
75
- const normalized = String(text ?? "").replace(/\s+/gu, " ").trim();
76
- if (
77
- !normalized
78
- || normalized.length > 180
79
- || String(text ?? "").split(/\r?\n/u).length > 2
80
- || ACTIONABLE_PROMPT.test(normalized)
81
- || EXPLICIT_THINKING_INTENT.test(normalized)
82
- ) return undefined;
83
-
84
- return LIGHTWEIGHT_STEER.test(normalized) ? "low" : undefined;
70
+ // A buffered steer belongs to an already-running work turn. It may add
71
+ // context, but must never replace that turn's model effort, tool catalog,
72
+ // history projection, or completion budget with the fast-chat profile.
73
+ void text;
74
+ void originKind;
75
+ return undefined;
85
76
  }
86
77
 
87
78
  module.exports = {
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
- const TOOL_SCHEMA_BUDGET_RATIO = 0.25;
4
- const TOOL_SCHEMA_MAX_TOKENS = 32_000;
3
+ const TOOL_SCHEMA_BUDGET_RATIO = 0.35;
4
+ const TOOL_SCHEMA_MAX_TOKENS = 64_000;
5
5
  const DEFERRED_TOOL_LOADER_NAME = 'ToolSearch';
6
- const MAX_PERSISTENT_DEFERRED_TOOLS = 4;
6
+ const MAX_PERSISTENT_DEFERRED_TOOLS = 12;
7
7
  const MAX_TOOL_SEARCH_QUERY_CHARS = 1_000;
8
- const MAX_AUTO_RANKED_TOOLS = 2;
8
+ const MAX_AUTO_RANKED_TOOLS = 6;
9
9
  const TOOL_RANK_EMBEDDED_CONTENT_RE = /<(attached_documents?|attachment_content|document_content|file_content)\b[^>]*>[\s\S]*?<\/\1>/giu;
10
10
  const TOOL_RANK_STOP_WORDS = new Set([
11
11
  'aber', 'also', 'and', 'aus', 'bitte', 'can', 'das', 'den', 'der', 'des', 'die', 'dies', 'diese',
@@ -469,6 +469,7 @@ module.exports = {
469
469
  CORE_TOOL_NAMES,
470
470
  DEFERRED_TOOL_LOADER_NAME,
471
471
  MAX_PERSISTENT_DEFERRED_TOOLS,
472
+ MAX_AUTO_RANKED_TOOLS,
472
473
  MAX_TOOL_SEARCH_QUERY_CHARS,
473
474
  TOOL_SCHEMA_BUDGET_RATIO,
474
475
  TOOL_SCHEMA_MAX_TOKENS,