newmark-agent 0.5.7 → 0.5.9
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/dist/cli-commands.js +3 -3
- package/dist/cli.js +1 -1
- package/dist/conversation-utility-host.bundle.cjs +830 -595
- package/dist/core/agent.d.ts +1 -1
- package/dist/core/agent.js +57 -5
- package/dist/core/agentKernelRunner.js +43 -8
- package/dist/core/emptyResponseRetry.d.ts +16 -0
- package/dist/core/emptyResponseRetry.js +25 -0
- package/dist/core/memoryLab.d.ts +17 -0
- package/dist/core/memoryLab.js +43 -1
- package/dist/core/terminalOutputBuffer.d.ts +24 -0
- package/dist/core/terminalOutputBuffer.js +92 -0
- package/dist/core/toolPolicy.d.ts +1 -0
- package/dist/core/toolPolicy.js +16 -0
- package/dist/core/types.d.ts +1 -1
- package/dist/llm/provider.d.ts +4 -4
- package/dist/llm/provider.js +55 -21
- package/dist/main.js +12 -3
- package/dist/providers/chat-completions.adapter.js +17 -4
- package/dist/providers/provider-events.d.ts +7 -0
- package/dist/providers/provider-events.js +40 -0
- package/dist/providers/responses.adapter.js +23 -9
- package/dist/tools/index.js +42 -10
- package/dist/ui/index.html +73 -19
- package/dist/wsl-agent-host.bundle.cjs +830 -595
- package/package.json +3 -1
package/dist/core/agent.d.ts
CHANGED
|
@@ -464,7 +464,7 @@ export declare class Agent {
|
|
|
464
464
|
markRouteToolExecuted(name: string, rawArgs?: string): void;
|
|
465
465
|
routeTransitionKind(): PlannedRouteAttempt['kind'] | '';
|
|
466
466
|
beginRouteAttempt(): void;
|
|
467
|
-
waitForPlannedRouteRetry(): Promise<void>;
|
|
467
|
+
waitForPlannedRouteRetry(explicitDelayMs?: number): Promise<void>;
|
|
468
468
|
recordRouteSuccess(latencyMs?: number, throughput?: number): void;
|
|
469
469
|
recordRouteToolOutcome(valid: boolean): void;
|
|
470
470
|
updateProviders(value: unknown): void;
|
package/dist/core/agent.js
CHANGED
|
@@ -355,7 +355,7 @@ class Agent {
|
|
|
355
355
|
this.config.set('skills', 'auto_download', 'disabled');
|
|
356
356
|
}
|
|
357
357
|
const modeStr = this.config.getStr('agent', 'default_mode');
|
|
358
|
-
this.mode = (['plan', 'goal', 'flow'].includes(modeStr) ? modeStr : 'build');
|
|
358
|
+
this.mode = (['plan', 'chat', 'goal', 'flow'].includes(modeStr) ? modeStr : 'build');
|
|
359
359
|
const inputStr = this.config.getStr('general', 'default_input');
|
|
360
360
|
this.inputMode = inputStr === 'next' ? 'next' : 'guide';
|
|
361
361
|
const configuredModel = this.config.getStr('models', 'default_model');
|
|
@@ -399,6 +399,8 @@ class Agent {
|
|
|
399
399
|
}
|
|
400
400
|
}
|
|
401
401
|
setMode(m) {
|
|
402
|
+
if (!['build', 'plan', 'chat', 'goal', 'flow'].includes(m))
|
|
403
|
+
m = 'build';
|
|
402
404
|
if (m === 'goal' && !this.goal) {
|
|
403
405
|
this.goal = new GoalStateImpl('Set your objective');
|
|
404
406
|
}
|
|
@@ -1016,7 +1018,13 @@ class Agent {
|
|
|
1016
1018
|
beginRouteAttempt() {
|
|
1017
1019
|
this.routeAttemptStartedAt = Date.now();
|
|
1018
1020
|
}
|
|
1019
|
-
async waitForPlannedRouteRetry() {
|
|
1021
|
+
async waitForPlannedRouteRetry(explicitDelayMs) {
|
|
1022
|
+
if (explicitDelayMs !== undefined) {
|
|
1023
|
+
if (explicitDelayMs <= 0)
|
|
1024
|
+
return;
|
|
1025
|
+
await new Promise(resolve => setTimeout(resolve, explicitDelayMs));
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1020
1028
|
const waitBudgetMs = Math.max(0, Math.min(15_000, this.lastRouteDecision?.retryBudgetMs ?? 5_000));
|
|
1021
1029
|
const delay = Math.max(0, Math.min(waitBudgetMs, this.lastRouteRetryDelayMs));
|
|
1022
1030
|
this.lastRouteRetryDelayMs = 0;
|
|
@@ -5356,7 +5364,24 @@ class Agent {
|
|
|
5356
5364
|
if (action !== 'update')
|
|
5357
5365
|
return JSON.stringify({ ok: false, error: `Unknown linked_plan action: ${action}` });
|
|
5358
5366
|
const expectedRevision = Number(input.expected_revision ?? input.expectedRevision);
|
|
5359
|
-
|
|
5367
|
+
const current = this.getLinkedPlan();
|
|
5368
|
+
let markdown = input.markdown === undefined ? current.markdown : String(input.markdown);
|
|
5369
|
+
if (input.append !== undefined)
|
|
5370
|
+
markdown = `${current.markdown}${String(input.append)}`;
|
|
5371
|
+
if (input.old_text !== undefined || input.oldText !== undefined) {
|
|
5372
|
+
const oldText = String(input.old_text ?? input.oldText ?? '');
|
|
5373
|
+
if (!oldText)
|
|
5374
|
+
throw new Error('linked_plan old_text must not be empty.');
|
|
5375
|
+
const matches = current.markdown.split(oldText).length - 1;
|
|
5376
|
+
if (!matches)
|
|
5377
|
+
throw new Error('linked_plan old_text was not found.');
|
|
5378
|
+
const replaceAll = input.replace_all === true || input.replaceAll === true;
|
|
5379
|
+
if (matches > 1 && !replaceAll)
|
|
5380
|
+
throw new Error(`linked_plan old_text matched ${matches} places; pass replace_all=true or a unique fragment.`);
|
|
5381
|
+
const newText = String(input.new_text ?? input.newText ?? '');
|
|
5382
|
+
markdown = replaceAll ? current.markdown.split(oldText).join(newText) : current.markdown.replace(oldText, newText);
|
|
5383
|
+
}
|
|
5384
|
+
return JSON.stringify({ ok: true, linkedPlan: this.updateLinkedPlan(markdown, expectedRevision) }, null, 2);
|
|
5360
5385
|
}
|
|
5361
5386
|
catch (error) {
|
|
5362
5387
|
return JSON.stringify({ ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
@@ -7959,7 +7984,24 @@ class Agent {
|
|
|
7959
7984
|
}));
|
|
7960
7985
|
}
|
|
7961
7986
|
case 'memory_lab_update': {
|
|
7962
|
-
const
|
|
7987
|
+
const selector = String(params.component || params.slug || '').trim();
|
|
7988
|
+
const prepared = selector ? this.memoryLab.preparePatch({
|
|
7989
|
+
component: selector,
|
|
7990
|
+
name: params.name === undefined ? undefined : String(params.name),
|
|
7991
|
+
description: params.description === undefined ? undefined : String(params.description),
|
|
7992
|
+
tags: params.tags === undefined ? undefined : (Array.isArray(params.tags) ? params.tags.map(String) : String(params.tags).split(/[,,\n]+/)),
|
|
7993
|
+
tagPaths: params.tagPaths === undefined ? undefined : (Array.isArray(params.tagPaths) ? params.tagPaths.filter(Array.isArray).map(pathValue => pathValue.map(String)) : []),
|
|
7994
|
+
content: params.content === undefined ? undefined : String(params.content),
|
|
7995
|
+
contentAppend: params.contentAppend === undefined && params.content_append === undefined ? undefined : String(params.contentAppend ?? params.content_append),
|
|
7996
|
+
oldText: params.oldText === undefined && params.old_text === undefined ? undefined : String(params.oldText ?? params.old_text),
|
|
7997
|
+
newText: String(params.newText ?? params.new_text ?? ''),
|
|
7998
|
+
replaceAll: params.replaceAll === true || params.replace_all === true,
|
|
7999
|
+
kind: params.kind === undefined ? undefined : (params.kind === 'folder' ? 'folder' : 'file'),
|
|
8000
|
+
expectedUpdatedAt: String(params.expectedUpdatedAt || params.expected_updated_at || ''),
|
|
8001
|
+
reason: String(params.reason || ''),
|
|
8002
|
+
source: String(params.source || ''),
|
|
8003
|
+
}) : undefined;
|
|
8004
|
+
const result = await this.updateMemoryLab(prepared || {
|
|
7963
8005
|
name: String(params.name || ''),
|
|
7964
8006
|
description: String(params.description || ''),
|
|
7965
8007
|
tags: Array.isArray(params.tags) ? params.tags.map(String) : String(params.tags || '').split(/[,,\n]+/),
|
|
@@ -9121,6 +9163,8 @@ class Agent {
|
|
|
9121
9163
|
parts.push(this.buildFeatureDisclosurePrompt());
|
|
9122
9164
|
if (this.mode === 'plan')
|
|
9123
9165
|
parts.push(`[Plan Tool Policy]\n${(0, toolPolicy_1.planModePolicyPrompt)()}`);
|
|
9166
|
+
if (this.mode === 'chat')
|
|
9167
|
+
parts.push(`[Chat Tool Policy]\n${(0, toolPolicy_1.chatModePolicyPrompt)()}`);
|
|
9124
9168
|
const pm = this.config.getStr('workspace', 'prompt_mode') || 'both';
|
|
9125
9169
|
const injectedPrompts = new Set();
|
|
9126
9170
|
if ((pm === 'global_only' || pm === 'both') && globalPrompt) {
|
|
@@ -9241,7 +9285,7 @@ class Agent {
|
|
|
9241
9285
|
`- Language policy: general.language=${language}; the UI can switch this at runtime and each turn must obey the current value. auto follows the user's dominant input language, en replies in English, zh replies in Simplified Chinese. Keep code, commands, file paths, JSON keys, model/provider names, tool names, quoted source text, and user-provided literals exactly as required by their source language.`,
|
|
9242
9286
|
`- Workspace permissions: access_permission=${permission}; file tools are checked before execution and blocked when they exceed the configured workspace boundary.`,
|
|
9243
9287
|
`- Remote repository safety: when the active workspace or any target path is inside a GitHub/remote-backed repository, proactively use repo_security_audit and file_audit before git_push, gh_pr_create, release packaging, public reporting, or cloud-side audit. Treat public remotes as public disclosure surfaces and keep private URLs, secrets, privacy addresses (credential URLs, private network addresses, local user paths), local runtime state, archives, Memory Lab, Work, config, and release outputs out of commits and summaries. git_push/gh_pr_create hard-block on detected high-risk findings until a second review resolves them and the action is retried with security_review_confirmed=true.`,
|
|
9244
|
-
`- Mode engine: current mode=${this.modeName()}; Build works autonomously, Plan is fully read-only
|
|
9288
|
+
`- Mode engine: current mode=${this.modeName()}; Build works autonomously, Plan is fully read-only, Chat only performs web search/fetch evidence gathering and prompt synthesis, Goal continues until completion unless paused, Flow follows saved workflow components.`,
|
|
9245
9289
|
`- Input mode: ${input}; Guide injects immediately, Next queues user intent for the following build turn.`,
|
|
9246
9290
|
`- Option feedback: ${this.buildQuestionPolicyPrompt(optionFeedback)}`,
|
|
9247
9291
|
`- Model policy: current model=${this.model || '(unset)'}, intelligence=${this.intelligence}, auto-switch=${modelSwitch}.`,
|
|
@@ -9312,6 +9356,14 @@ class Agent {
|
|
|
9312
9356
|
'Only after the durable linked plan has actually been updated and the plan is complete, expose the fixed mode handoff asking whether execution should begin. Offer exactly these two choices in the user language: "是,执行此计划" / "否,请补充____" (or "Yes, execute this plan" / "No, please supplement _____"). This fixed handoff remains required when discretionary questions are disabled.',
|
|
9313
9357
|
'A positive choice starts a new Build-mode input. A negative choice remains in Plan mode so the user can supply the missing details.',
|
|
9314
9358
|
]).join('\n');
|
|
9359
|
+
case 'chat':
|
|
9360
|
+
return withLanguage([
|
|
9361
|
+
'CHAT MODE.',
|
|
9362
|
+
'Use only web_search and web_fetch. You have no workspace, host, application, memory, task, browser-control, or write permissions.',
|
|
9363
|
+
'Perform an online search to gather evidence before answering. Fetch primary or authoritative pages when the search snippets are insufficient.',
|
|
9364
|
+
'After sufficient evidence is collected, summarize and answer the user as soon as possible. Stay concise and do not turn the request into a long-running Build, Plan, Goal, or Flow task.',
|
|
9365
|
+
'Distinguish sourced facts from uncertainty and include useful source links in the final answer.',
|
|
9366
|
+
]).join('\n');
|
|
9315
9367
|
case 'goal': {
|
|
9316
9368
|
const g = this.goal?.history() || '';
|
|
9317
9369
|
const paused = this.goal?.paused ? '\n[GOAL PAUSED by user. Wait for resume.]' : '\n[Continue working until the goal is achieved.]';
|
|
@@ -46,6 +46,7 @@ const toolPolicy_1 = require("./toolPolicy");
|
|
|
46
46
|
const performanceDiagnostics_1 = require("./performanceDiagnostics");
|
|
47
47
|
const agentKernelDiagnostics_1 = require("./agentKernelDiagnostics");
|
|
48
48
|
const toolchain_1 = require("../toolchain");
|
|
49
|
+
const emptyResponseRetry_1 = require("./emptyResponseRetry");
|
|
49
50
|
const publicStreamFilters = new WeakMap();
|
|
50
51
|
const brokerOnlyAssistantBuffers = new WeakMap();
|
|
51
52
|
const BROKER_PREFACE_BUFFER_CHARS = 96;
|
|
@@ -206,7 +207,7 @@ function kernelTurnFailed(agent, turn) {
|
|
|
206
207
|
return turn.stopReason === 'error' || agent.isLlmErrorText(turn.text);
|
|
207
208
|
}
|
|
208
209
|
function providerTurnIsEmpty(turn) {
|
|
209
|
-
return /provider returned an empty response/i.test(`${turn.errorMessage}\n${turn.text}`);
|
|
210
|
+
return !turn.activity && /provider returned an empty response/i.test(`${turn.errorMessage}\n${turn.text}`);
|
|
210
211
|
}
|
|
211
212
|
function removeTrailingFailedAssistant(agent, messages) {
|
|
212
213
|
const last = messages[messages.length - 1];
|
|
@@ -216,6 +217,14 @@ function removeTrailingFailedAssistant(agent, messages) {
|
|
|
216
217
|
if (last.stopReason === 'error' || agent.isLlmErrorText(text))
|
|
217
218
|
messages.pop();
|
|
218
219
|
}
|
|
220
|
+
function removeTrailingThoughtOnlyAssistant(messages) {
|
|
221
|
+
const last = messages[messages.length - 1];
|
|
222
|
+
if (last?.role !== 'assistant')
|
|
223
|
+
return;
|
|
224
|
+
const hasToolCall = last.content.some(content => content.type === 'toolCall');
|
|
225
|
+
if (!KernelMessageText(last).trim() && !hasToolCall)
|
|
226
|
+
messages.pop();
|
|
227
|
+
}
|
|
219
228
|
function normalizePublicProviderError(error, secrets = []) {
|
|
220
229
|
let raw = '';
|
|
221
230
|
if (error instanceof Error) {
|
|
@@ -369,8 +378,22 @@ async function runAgentKernel(agent) {
|
|
|
369
378
|
const tokens = [];
|
|
370
379
|
const runOnce = async (promptMessages, appendPromptToAgentHistory) => {
|
|
371
380
|
let lastAssistant = null;
|
|
381
|
+
let observedActivity = false;
|
|
382
|
+
let observedThought = false;
|
|
372
383
|
const unsubscribe = kernel.subscribe(async (event) => {
|
|
373
384
|
await handleKernelEvent(agent, event, tokens);
|
|
385
|
+
if (event.type === 'message_update') {
|
|
386
|
+
const delta = event.assistantMessageEvent;
|
|
387
|
+
const deltaText = typeof delta.delta === 'string'
|
|
388
|
+
? delta.delta
|
|
389
|
+
: '';
|
|
390
|
+
const thoughtDelta = delta.type === 'thinking_delta' && !!deltaText.trim();
|
|
391
|
+
observedThought = observedThought || thoughtDelta;
|
|
392
|
+
observedActivity = observedActivity ||
|
|
393
|
+
thoughtDelta ||
|
|
394
|
+
(delta.type === 'text_delta' && !!deltaText.trim()) ||
|
|
395
|
+
(delta.type === 'toolcall_end');
|
|
396
|
+
}
|
|
374
397
|
if (event.type === 'message_end' && event.message.role === 'assistant') {
|
|
375
398
|
lastAssistant = event.message;
|
|
376
399
|
}
|
|
@@ -390,11 +413,14 @@ async function runAgentKernel(agent) {
|
|
|
390
413
|
const text = assistant ? KernelMessageText(assistant) : '';
|
|
391
414
|
const hasToolCall = !!assistant?.content?.some(content => content.type === 'toolCall');
|
|
392
415
|
const emptyResponse = !assistant
|
|
393
|
-
|| (!text.trim() && !hasToolCall && String(assistant?.stopReason || '') !== 'aborted');
|
|
416
|
+
|| (!text.trim() && !hasToolCall && !observedActivity && String(assistant?.stopReason || '') !== 'aborted');
|
|
394
417
|
return {
|
|
395
418
|
text: emptyResponse ? '[Error] Provider returned an empty response.' : text,
|
|
396
419
|
stopReason: String(assistant?.stopReason || ''),
|
|
397
420
|
errorMessage: String(assistant?.errorMessage || (emptyResponse ? 'Provider returned an empty response.' : '')),
|
|
421
|
+
activity: observedActivity || !!text.trim() || hasToolCall,
|
|
422
|
+
thoughtOnly: observedThought && !text.trim() && !hasToolCall
|
|
423
|
+
&& !['error', 'aborted'].includes(String(assistant?.stopReason || '')),
|
|
398
424
|
};
|
|
399
425
|
}
|
|
400
426
|
finally {
|
|
@@ -448,14 +474,23 @@ async function runAgentKernel(agent) {
|
|
|
448
474
|
fallback: { from: modelBeforeKernelRun, to: agent.model, providerId: agent.activeDeployment()?.providerId },
|
|
449
475
|
});
|
|
450
476
|
}
|
|
451
|
-
let
|
|
452
|
-
|
|
477
|
+
let consecutiveEmptyResponses = 0;
|
|
478
|
+
for (;;) {
|
|
479
|
+
const emptyResponseState = (0, emptyResponseRetry_1.observeEmptyResponseOutcome)(consecutiveEmptyResponses, providerTurnIsEmpty(lastTurn));
|
|
480
|
+
consecutiveEmptyResponses = emptyResponseState.consecutiveEmptyResponses;
|
|
481
|
+
if (lastTurn.thoughtOnly) {
|
|
482
|
+
removeTrailingThoughtOnlyAssistant(kernel.state.messages);
|
|
483
|
+
lastTurn = await runWithCompressionResume([], false);
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
if (!emptyResponseState.retry)
|
|
487
|
+
break;
|
|
453
488
|
removeTrailingFailedAssistant(agent, kernel.state.messages);
|
|
454
|
-
|
|
455
|
-
const notice = `[Model retry] Provider returned an empty response; retrying the same deployment (${
|
|
489
|
+
const retryNumber = consecutiveEmptyResponses;
|
|
490
|
+
const notice = `[Model retry] Provider returned an empty response; retrying the same deployment (${retryNumber}/${emptyResponseRetry_1.MAX_EMPTY_RESPONSE_RETRIES}) after ${(0, emptyResponseRetry_1.emptyResponseRetryDelayMs)(consecutiveEmptyResponses)}ms.`;
|
|
456
491
|
tokens.push({ type: 'text', text: notice });
|
|
457
492
|
agent.recordWorkStatus(notice);
|
|
458
|
-
await agent.waitForPlannedRouteRetry();
|
|
493
|
+
await agent.waitForPlannedRouteRetry((0, emptyResponseRetry_1.emptyResponseRetryDelayMs)(consecutiveEmptyResponses));
|
|
459
494
|
lastTurn = await runWithCompressionResume([], false);
|
|
460
495
|
}
|
|
461
496
|
let routeRetries = 0;
|
|
@@ -667,7 +702,7 @@ async function runAgentKernel(agent) {
|
|
|
667
702
|
}
|
|
668
703
|
if (textStarted)
|
|
669
704
|
finalContent.push({ type: 'text', text });
|
|
670
|
-
if (!finalContent.length) {
|
|
705
|
+
if (!finalContent.length && !thinking.trim()) {
|
|
671
706
|
text = '[Error] Provider returned an empty response.';
|
|
672
707
|
finalContent.push({ type: 'text', text });
|
|
673
708
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const EMPTY_RESPONSE_RETRY_DELAYS_MS: readonly [200, 800, 2000, 10000, 60000];
|
|
2
|
+
/**
|
|
3
|
+
* A retry is scheduled only after an explicit provider empty-response
|
|
4
|
+
* failure. The initial failed request is not called a retry, so five retries
|
|
5
|
+
* means six consecutive explicit failures before termination.
|
|
6
|
+
*/
|
|
7
|
+
export declare const MAX_EMPTY_RESPONSE_RETRIES: 5;
|
|
8
|
+
export declare const MAX_CONSECUTIVE_EMPTY_RESPONSES: number;
|
|
9
|
+
export declare function emptyResponseRetryDelayMs(consecutiveEmptyResponses: number): number;
|
|
10
|
+
export interface EmptyResponseRetryState {
|
|
11
|
+
consecutiveEmptyResponses: number;
|
|
12
|
+
retry: boolean;
|
|
13
|
+
terminate: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function observeEmptyResponseOutcome(consecutiveEmptyResponses: number, emptyResponse: boolean): EmptyResponseRetryState;
|
|
16
|
+
//# sourceMappingURL=emptyResponseRetry.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAX_CONSECUTIVE_EMPTY_RESPONSES = exports.MAX_EMPTY_RESPONSE_RETRIES = exports.EMPTY_RESPONSE_RETRY_DELAYS_MS = void 0;
|
|
4
|
+
exports.emptyResponseRetryDelayMs = emptyResponseRetryDelayMs;
|
|
5
|
+
exports.observeEmptyResponseOutcome = observeEmptyResponseOutcome;
|
|
6
|
+
exports.EMPTY_RESPONSE_RETRY_DELAYS_MS = [200, 800, 2_000, 10_000, 60_000];
|
|
7
|
+
/**
|
|
8
|
+
* A retry is scheduled only after an explicit provider empty-response
|
|
9
|
+
* failure. The initial failed request is not called a retry, so five retries
|
|
10
|
+
* means six consecutive explicit failures before termination.
|
|
11
|
+
*/
|
|
12
|
+
exports.MAX_EMPTY_RESPONSE_RETRIES = exports.EMPTY_RESPONSE_RETRY_DELAYS_MS.length;
|
|
13
|
+
exports.MAX_CONSECUTIVE_EMPTY_RESPONSES = exports.MAX_EMPTY_RESPONSE_RETRIES + 1;
|
|
14
|
+
function emptyResponseRetryDelayMs(consecutiveEmptyResponses) {
|
|
15
|
+
return exports.EMPTY_RESPONSE_RETRY_DELAYS_MS[Math.max(0, consecutiveEmptyResponses - 1)] ?? 0;
|
|
16
|
+
}
|
|
17
|
+
function observeEmptyResponseOutcome(consecutiveEmptyResponses, emptyResponse) {
|
|
18
|
+
const nextCount = emptyResponse ? consecutiveEmptyResponses + 1 : 0;
|
|
19
|
+
return {
|
|
20
|
+
consecutiveEmptyResponses: nextCount,
|
|
21
|
+
retry: emptyResponse && nextCount <= exports.MAX_EMPTY_RESPONSE_RETRIES,
|
|
22
|
+
terminate: emptyResponse && nextCount > exports.MAX_EMPTY_RESPONSE_RETRIES,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=emptyResponseRetry.js.map
|
package/dist/core/memoryLab.d.ts
CHANGED
|
@@ -35,6 +35,22 @@ export interface MemoryLabUpdateInput {
|
|
|
35
35
|
reason?: string;
|
|
36
36
|
source?: string;
|
|
37
37
|
}
|
|
38
|
+
export interface MemoryLabPatchInput {
|
|
39
|
+
component: string;
|
|
40
|
+
name?: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
tags?: string[];
|
|
43
|
+
tagPaths?: string[][];
|
|
44
|
+
content?: string;
|
|
45
|
+
contentAppend?: string;
|
|
46
|
+
oldText?: string;
|
|
47
|
+
newText?: string;
|
|
48
|
+
replaceAll?: boolean;
|
|
49
|
+
kind?: MemoryLabComponentKind;
|
|
50
|
+
expectedUpdatedAt?: string;
|
|
51
|
+
reason?: string;
|
|
52
|
+
source?: string;
|
|
53
|
+
}
|
|
38
54
|
export interface MemoryLabPreparedUpdate extends MemoryLabUpdateInput {
|
|
39
55
|
slug: string;
|
|
40
56
|
description: string;
|
|
@@ -120,6 +136,7 @@ export declare class MemoryLabManager {
|
|
|
120
136
|
read(componentSelector?: string): MemoryLabReadResult;
|
|
121
137
|
visualizationSnapshot(): MemoryLabVisualizationResult;
|
|
122
138
|
prepareUpdate(input: MemoryLabUpdateInput): MemoryLabPreparedUpdate;
|
|
139
|
+
preparePatch(input: MemoryLabPatchInput): MemoryLabPreparedUpdate;
|
|
123
140
|
update(prepared: MemoryLabPreparedUpdate): MemoryLabWriteResult;
|
|
124
141
|
query(input: {
|
|
125
142
|
query: string;
|
package/dist/core/memoryLab.js
CHANGED
|
@@ -91,7 +91,8 @@ class MemoryLabManager {
|
|
|
91
91
|
'Use memory_lab_read to inspect index.json before deciding what memory is relevant.',
|
|
92
92
|
'Use memory_lab_query for bounded task-relevant retrieval; do not inject the complete index when a focused query is sufficient.',
|
|
93
93
|
'Use memory_lab_read with component/name/slug to read a component core markdown file.',
|
|
94
|
-
'Use memory_lab_update only when the user asks to create or update durable memory
|
|
94
|
+
'Use memory_lab_update only when the user asks to create or update durable memory. Create with name, tags, and content; patch an existing component with component plus only changed fields.',
|
|
95
|
+
'For small body edits prefer contentAppend or oldText/newText over resending the complete content.',
|
|
95
96
|
'For an existing component, pass expectedUpdatedAt from the latest read/query result. A stale update is rejected instead of overwriting newer memory.',
|
|
96
97
|
'Use memory_lab_delete only when the user explicitly asks to forget/remove durable memory. Delete moves the prior revision to Memory Lab/archive and records a policy event.',
|
|
97
98
|
'Every mutation should include a concise reason and source. ADD, UPDATE, and DELETE decisions are append-only in policy.jsonl and are recoverable from archive.',
|
|
@@ -206,6 +207,47 @@ class MemoryLabManager {
|
|
|
206
207
|
source: String(input.source || '').trim(),
|
|
207
208
|
};
|
|
208
209
|
}
|
|
210
|
+
preparePatch(input) {
|
|
211
|
+
const selector = String(input.component || '').trim();
|
|
212
|
+
if (!selector)
|
|
213
|
+
throw new Error('Memory component is required for a patch.');
|
|
214
|
+
const current = this.read(selector);
|
|
215
|
+
if (!current.ok || !current.component)
|
|
216
|
+
throw new Error(current.error || `Memory component not found: ${selector}`);
|
|
217
|
+
const existing = current.component.meta;
|
|
218
|
+
const oldContent = current.component.content;
|
|
219
|
+
let content = input.content !== undefined ? String(input.content) : oldContent;
|
|
220
|
+
if (input.contentAppend !== undefined)
|
|
221
|
+
content = `${oldContent}${String(input.contentAppend)}`;
|
|
222
|
+
if (input.oldText !== undefined) {
|
|
223
|
+
const oldText = String(input.oldText);
|
|
224
|
+
if (!oldText)
|
|
225
|
+
throw new Error('oldText must not be empty.');
|
|
226
|
+
const matches = oldContent.split(oldText).length - 1;
|
|
227
|
+
if (!matches)
|
|
228
|
+
throw new Error('oldText was not found in the Memory Lab component.');
|
|
229
|
+
if (matches > 1 && input.replaceAll !== true)
|
|
230
|
+
throw new Error(`oldText matched ${matches} places; pass replaceAll=true or a unique fragment.`);
|
|
231
|
+
content = input.replaceAll === true
|
|
232
|
+
? oldContent.split(oldText).join(String(input.newText || ''))
|
|
233
|
+
: oldContent.replace(oldText, String(input.newText || ''));
|
|
234
|
+
}
|
|
235
|
+
const name = input.name === undefined ? existing.name : String(input.name);
|
|
236
|
+
if (this.slugify(name) !== current.component.slug) {
|
|
237
|
+
throw new Error('Renaming a Memory Lab component is not supported by incremental patch; create the new component then delete the old one.');
|
|
238
|
+
}
|
|
239
|
+
return this.prepareUpdate({
|
|
240
|
+
name,
|
|
241
|
+
description: input.description === undefined ? existing.description : String(input.description),
|
|
242
|
+
tags: input.tags === undefined ? existing.tags : input.tags,
|
|
243
|
+
tagPaths: input.tagPaths === undefined ? existing.tagPaths : input.tagPaths,
|
|
244
|
+
content,
|
|
245
|
+
kind: input.kind === undefined ? existing.kind : input.kind,
|
|
246
|
+
expectedUpdatedAt: String(input.expectedUpdatedAt || existing.updatedAt),
|
|
247
|
+
reason: input.reason,
|
|
248
|
+
source: input.source,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
209
251
|
update(prepared) {
|
|
210
252
|
this.ensure();
|
|
211
253
|
const index = this.loadIndex();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare const TERMINAL_HISTORY_LIMIT: number;
|
|
2
|
+
export interface TerminalOutputBufferOptions {
|
|
3
|
+
flushIntervalMs?: number;
|
|
4
|
+
historyLimit?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class TerminalOutputBuffer {
|
|
7
|
+
private readonly send;
|
|
8
|
+
private readonly sessions;
|
|
9
|
+
private timer;
|
|
10
|
+
private readonly flushIntervalMs;
|
|
11
|
+
private readonly historyLimit;
|
|
12
|
+
constructor(send: (sessionId: string, text: string) => void, options?: TerminalOutputBufferOptions);
|
|
13
|
+
push(sessionId: string, text: string): void;
|
|
14
|
+
flush(sessionId: string): void;
|
|
15
|
+
flushAll(): void;
|
|
16
|
+
close(sessionId: string): string;
|
|
17
|
+
history(sessionId: string): string;
|
|
18
|
+
pendingChunkCount(): number;
|
|
19
|
+
hasScheduledFlush(): boolean;
|
|
20
|
+
private bound;
|
|
21
|
+
private schedule;
|
|
22
|
+
private clearTimerWhenIdle;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=terminalOutputBuffer.d.ts.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TerminalOutputBuffer = exports.TERMINAL_HISTORY_LIMIT = void 0;
|
|
4
|
+
exports.TERMINAL_HISTORY_LIMIT = 256 * 1024;
|
|
5
|
+
class TerminalOutputBuffer {
|
|
6
|
+
send;
|
|
7
|
+
sessions = new Map();
|
|
8
|
+
timer = null;
|
|
9
|
+
flushIntervalMs;
|
|
10
|
+
historyLimit;
|
|
11
|
+
constructor(send, options = {}) {
|
|
12
|
+
this.send = send;
|
|
13
|
+
this.flushIntervalMs = Math.max(1, options.flushIntervalMs ?? 20);
|
|
14
|
+
this.historyLimit = Math.max(1, options.historyLimit ?? exports.TERMINAL_HISTORY_LIMIT);
|
|
15
|
+
}
|
|
16
|
+
push(sessionId, text) {
|
|
17
|
+
if (!text)
|
|
18
|
+
return;
|
|
19
|
+
let state = this.sessions.get(sessionId);
|
|
20
|
+
if (!state) {
|
|
21
|
+
state = { chunks: [], length: 0, history: '' };
|
|
22
|
+
this.sessions.set(sessionId, state);
|
|
23
|
+
}
|
|
24
|
+
state.chunks.push(text);
|
|
25
|
+
state.length += text.length;
|
|
26
|
+
this.schedule();
|
|
27
|
+
}
|
|
28
|
+
flush(sessionId) {
|
|
29
|
+
const state = this.sessions.get(sessionId);
|
|
30
|
+
if (!state?.length)
|
|
31
|
+
return;
|
|
32
|
+
const text = state.chunks.length === 1 ? state.chunks[0] : state.chunks.join('');
|
|
33
|
+
state.chunks = [];
|
|
34
|
+
state.length = 0;
|
|
35
|
+
state.history = this.bound(`${state.history}${text}`);
|
|
36
|
+
this.send(sessionId, text);
|
|
37
|
+
this.clearTimerWhenIdle();
|
|
38
|
+
}
|
|
39
|
+
flushAll() {
|
|
40
|
+
for (const sessionId of this.sessions.keys())
|
|
41
|
+
this.flush(sessionId);
|
|
42
|
+
this.clearTimerWhenIdle(true);
|
|
43
|
+
}
|
|
44
|
+
close(sessionId) {
|
|
45
|
+
this.flush(sessionId);
|
|
46
|
+
const history = this.sessions.get(sessionId)?.history ?? '';
|
|
47
|
+
this.sessions.delete(sessionId);
|
|
48
|
+
this.clearTimerWhenIdle();
|
|
49
|
+
return history;
|
|
50
|
+
}
|
|
51
|
+
history(sessionId) {
|
|
52
|
+
const state = this.sessions.get(sessionId);
|
|
53
|
+
if (!state)
|
|
54
|
+
return '';
|
|
55
|
+
if (!state.length)
|
|
56
|
+
return state.history;
|
|
57
|
+
const pending = state.chunks.length === 1 ? state.chunks[0] : state.chunks.join('');
|
|
58
|
+
return this.bound(`${state.history}${pending}`);
|
|
59
|
+
}
|
|
60
|
+
pendingChunkCount() {
|
|
61
|
+
let count = 0;
|
|
62
|
+
for (const state of this.sessions.values())
|
|
63
|
+
count += state.chunks.length;
|
|
64
|
+
return count;
|
|
65
|
+
}
|
|
66
|
+
hasScheduledFlush() {
|
|
67
|
+
return this.timer !== null;
|
|
68
|
+
}
|
|
69
|
+
bound(text) {
|
|
70
|
+
return text.length > this.historyLimit ? text.slice(-this.historyLimit) : text;
|
|
71
|
+
}
|
|
72
|
+
schedule() {
|
|
73
|
+
if (this.timer)
|
|
74
|
+
return;
|
|
75
|
+
this.timer = setTimeout(() => {
|
|
76
|
+
this.timer = null;
|
|
77
|
+
this.flushAll();
|
|
78
|
+
}, this.flushIntervalMs);
|
|
79
|
+
this.timer.unref?.();
|
|
80
|
+
}
|
|
81
|
+
clearTimerWhenIdle(force = false) {
|
|
82
|
+
if (!this.timer)
|
|
83
|
+
return;
|
|
84
|
+
const hasPending = !force && Array.from(this.sessions.values()).some(state => state.length > 0);
|
|
85
|
+
if (hasPending)
|
|
86
|
+
return;
|
|
87
|
+
clearTimeout(this.timer);
|
|
88
|
+
this.timer = null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
exports.TerminalOutputBuffer = TerminalOutputBuffer;
|
|
92
|
+
//# sourceMappingURL=terminalOutputBuffer.js.map
|
|
@@ -25,6 +25,7 @@ export declare function toolAvailability(name: string): ToolAvailability;
|
|
|
25
25
|
export declare function evaluateToolPolicy(request: ToolPolicyRequest): ToolPolicyDecision;
|
|
26
26
|
export declare function filterToolDefinitions<T>(definitions: T[], request: Omit<ToolPolicyRequest, 'name' | 'args'>): T[];
|
|
27
27
|
export declare function planModePolicyPrompt(): string;
|
|
28
|
+
export declare function chatModePolicyPrompt(): string;
|
|
28
29
|
export interface DeletionGuardDecision {
|
|
29
30
|
blocked: boolean;
|
|
30
31
|
reason?: string;
|
package/dist/core/toolPolicy.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.toolAvailability = toolAvailability;
|
|
|
7
7
|
exports.evaluateToolPolicy = evaluateToolPolicy;
|
|
8
8
|
exports.filterToolDefinitions = filterToolDefinitions;
|
|
9
9
|
exports.planModePolicyPrompt = planModePolicyPrompt;
|
|
10
|
+
exports.chatModePolicyPrompt = chatModePolicyPrompt;
|
|
10
11
|
exports.evaluateDeletionGuard = evaluateDeletionGuard;
|
|
11
12
|
const REQUIRED_TOOLS = new Set(['pwd', 'read', 'glob', 'grep']);
|
|
12
13
|
const MODE_SCOPED_TOOLS = new Set([
|
|
@@ -81,6 +82,7 @@ exports.PLAN_COMPUTER_USE_ACTIONS = ['observe', 'app_list', 'app_observe'];
|
|
|
81
82
|
exports.PLAN_BROWSER_USE_ACTIONS = ['observe', 'navigate', 'wait', 'extract'];
|
|
82
83
|
const PLAN_COMPUTER_USE_ACTION_SET = new Set(exports.PLAN_COMPUTER_USE_ACTIONS);
|
|
83
84
|
const PLAN_BROWSER_USE_ACTION_SET = new Set(exports.PLAN_BROWSER_USE_ACTIONS);
|
|
85
|
+
const CHAT_WEB_TOOLS = new Set(['web_search', 'web_fetch']);
|
|
84
86
|
/**
|
|
85
87
|
* 并发安全工具集合(DSH isConcurrencySafe 语义的 Newmark 落地)。
|
|
86
88
|
*
|
|
@@ -140,6 +142,13 @@ function evaluateToolPolicy(request) {
|
|
|
140
142
|
const base = { availability, settingsVisible: availability === 'configurable' };
|
|
141
143
|
if (!name)
|
|
142
144
|
return { ...base, allowed: false, reason: '[permission] Tool name is required.' };
|
|
145
|
+
if (request.mode === 'chat' && !CHAT_WEB_TOOLS.has(name)) {
|
|
146
|
+
return {
|
|
147
|
+
...base,
|
|
148
|
+
allowed: false,
|
|
149
|
+
reason: `[permission] Chat mode only allows web_search and web_fetch. It has no workspace, host, application, memory, task, or other write access. Blocked: ${name}`,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
143
152
|
if (request.mode === 'plan') {
|
|
144
153
|
if (name === 'computer_use') {
|
|
145
154
|
const action = String(request.args?.action || '').trim();
|
|
@@ -182,6 +191,13 @@ function planModePolicyPrompt() {
|
|
|
182
191
|
'Runtime policy rejects stale or hidden mutating tool calls even if a prompt asks for them.',
|
|
183
192
|
].join(' ');
|
|
184
193
|
}
|
|
194
|
+
function chatModePolicyPrompt() {
|
|
195
|
+
return [
|
|
196
|
+
'Chat mode is a narrow web-evidence mode.',
|
|
197
|
+
'Only web_search and web_fetch are available; every workspace, host, application, memory, task, browser-control, and write capability is denied at runtime.',
|
|
198
|
+
'Search the web for relevant evidence, fetch primary or authoritative sources when useful, then summarize and answer promptly instead of expanding into a long-running task.',
|
|
199
|
+
].join(' ');
|
|
200
|
+
}
|
|
185
201
|
/** 删除命令动词(跨 POSIX / PowerShell / cmd)。注意:不含单独 "remove"(避免匹配普通英文)。 */
|
|
186
202
|
const DELETE_VERB_SOURCE = '(?:remove-item|rmdir|unlink|erase|del|rm|rd|ri)';
|
|
187
203
|
const DELETE_VERB_BOUNDARY = new RegExp(`(?:^|[\\s;&|()\\n])${DELETE_VERB_SOURCE}(?:\\s|$)`, 'i');
|
package/dist/core/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type AgentMode = 'build' | 'plan' | 'goal' | 'flow';
|
|
1
|
+
export type AgentMode = 'build' | 'plan' | 'chat' | 'goal' | 'flow';
|
|
2
2
|
export type InputMode = 'guide' | 'next';
|
|
3
3
|
export type AgentStatus = 'idle' | 'working' | 'error' | 'goal_paused';
|
|
4
4
|
/** Public routing identity used by renderer/main/runtime envelopes. */
|
package/dist/llm/provider.d.ts
CHANGED
|
@@ -96,7 +96,7 @@ export declare class LLMProvider {
|
|
|
96
96
|
* `provider_adapters_v2` context flag. Request serialization and SSE
|
|
97
97
|
* normalization are delegated to the shared provider adapters while the
|
|
98
98
|
* transport orchestration (loopback node-http, fetch -> node-http fallback,
|
|
99
|
-
*
|
|
99
|
+
* cancellation-only streaming and the 4xx Chat -> Responses downgrade) stay here.
|
|
100
100
|
* The emitted request body and StreamToken stream are byte-equivalent to
|
|
101
101
|
* the legacy inlined path.
|
|
102
102
|
*/
|
|
@@ -106,9 +106,9 @@ export declare class LLMProvider {
|
|
|
106
106
|
private shouldDowngradeToResponses;
|
|
107
107
|
/**
|
|
108
108
|
* Loopback-aware transport injected into adapter `execute`. Streaming
|
|
109
|
-
* requests retain the fetch-to-node fallback for transport failures
|
|
110
|
-
*
|
|
111
|
-
*
|
|
109
|
+
* requests retain the fetch-to-node fallback for transport failures. They
|
|
110
|
+
* have no response deadline; only caller cancellation or a concrete
|
|
111
|
+
* transport/provider failure may end the request.
|
|
112
112
|
*/
|
|
113
113
|
private buildProviderAdapterTransport;
|
|
114
114
|
private toTransportResponse;
|