newmark-agent 0.3.8 → 0.3.10
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/context/domain/types.d.ts +1 -1
- package/dist/conversation-utility-host.bundle.cjs +1869 -721
- package/dist/conversation-utility-host.js +4 -1
- package/dist/core/agent.d.ts +23 -1
- package/dist/core/agent.js +495 -33
- package/dist/core/agentKernelRunner.js +1 -1
- package/dist/core/compressionHistoryArchive.d.ts +28 -0
- package/dist/core/compressionHistoryArchive.js +131 -0
- package/dist/core/config.js +1 -0
- package/dist/core/conversationKernel.d.ts +2 -1
- package/dist/core/conversationKernel.js +38 -7
- package/dist/core/electronBrowserUseHost.js +9 -0
- package/dist/core/electronUtilityRuntimePool.js +6 -7
- package/dist/core/runtimeLifecycle.d.ts +23 -0
- package/dist/core/runtimeLifecycle.js +146 -0
- package/dist/core/types.d.ts +3 -0
- package/dist/core/wslAgentRuntimePool.js +9 -10
- package/dist/main.js +15 -0
- package/dist/tools/index.js +6 -3
- package/dist/tools/nativeTools.js +1 -1
- package/dist/ui/index.html +0 -1
- package/dist/wsl-agent-host.bundle.cjs +1872 -723
- package/dist/wsl-agent-host.js +4 -0
- package/package.json +2 -2
|
@@ -401,7 +401,7 @@ async function runAgentKernel(agent) {
|
|
|
401
401
|
throw new ProviderRunError(normalizePublicProviderError(lastTurn.errorMessage || lastTurn.text, [agent.activeModelConfig()?.api_key]));
|
|
402
402
|
}
|
|
403
403
|
const lastAssistant = lastTurn.text;
|
|
404
|
-
if (agent.mode === 'goal' && agent.goal && agent.goal.checkComplete(lastAssistant)) {
|
|
404
|
+
if (agent.mode === 'goal' && agent.goal && !agent.goal.paused && agent.goal.checkComplete(lastAssistant)) {
|
|
405
405
|
agent.markGoalComplete();
|
|
406
406
|
if (!tokens.some(token => token.type === 'text' && /goal complete/i.test(token.text || ''))) {
|
|
407
407
|
tokens.push({ type: 'text', text: '\n[Goal Complete]' });
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface ArchivedCompressionEntry {
|
|
2
|
+
id: string;
|
|
3
|
+
at: string;
|
|
4
|
+
summary: string;
|
|
5
|
+
messages: Array<Record<string, unknown>>;
|
|
6
|
+
foldedEntries: number;
|
|
7
|
+
foldedChars: number;
|
|
8
|
+
model: string;
|
|
9
|
+
fallback: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Append-only cold storage for folded context segments evicted from the small
|
|
13
|
+
* in-state hot cache. The archive is never injected into a model request;
|
|
14
|
+
* callers must explicitly search/read/restore one bounded segment.
|
|
15
|
+
*/
|
|
16
|
+
export declare class CompressionHistoryArchive {
|
|
17
|
+
private readonly rootPath;
|
|
18
|
+
constructor(rootPath: string);
|
|
19
|
+
private file;
|
|
20
|
+
private append;
|
|
21
|
+
archive(scopeKey: string, entry: ArchivedCompressionEntry): void;
|
|
22
|
+
markRestored(scopeKey: string, id: string): void;
|
|
23
|
+
/** Replays the append-only ledger and returns only currently restorable entries. */
|
|
24
|
+
activeEntries(scopeKey: string): ArchivedCompressionEntry[];
|
|
25
|
+
/** Includes restored/tombstoned ids so a restart never reuses an archive id. */
|
|
26
|
+
maxNumericId(scopeKey: string): number;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=compressionHistoryArchive.d.ts.map
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.CompressionHistoryArchive = void 0;
|
|
37
|
+
const crypto = __importStar(require("crypto"));
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
/**
|
|
41
|
+
* Append-only cold storage for folded context segments evicted from the small
|
|
42
|
+
* in-state hot cache. The archive is never injected into a model request;
|
|
43
|
+
* callers must explicitly search/read/restore one bounded segment.
|
|
44
|
+
*/
|
|
45
|
+
class CompressionHistoryArchive {
|
|
46
|
+
rootPath;
|
|
47
|
+
constructor(rootPath) {
|
|
48
|
+
this.rootPath = rootPath;
|
|
49
|
+
}
|
|
50
|
+
file(scopeKey) {
|
|
51
|
+
const digest = crypto.createHash('sha256').update(scopeKey).digest('hex');
|
|
52
|
+
return path.join(this.rootPath, '.newmark-context-v2', 'compression-history', `${digest}.jsonl`);
|
|
53
|
+
}
|
|
54
|
+
append(scopeKey, event) {
|
|
55
|
+
const file = this.file(scopeKey);
|
|
56
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
57
|
+
fs.appendFileSync(file, `${JSON.stringify(event)}\n`, 'utf-8');
|
|
58
|
+
}
|
|
59
|
+
archive(scopeKey, entry) {
|
|
60
|
+
this.append(scopeKey, {
|
|
61
|
+
version: 1,
|
|
62
|
+
type: 'fold',
|
|
63
|
+
at: new Date().toISOString(),
|
|
64
|
+
entry: { ...entry, messages: entry.messages.map(message => ({ ...message })) },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
markRestored(scopeKey, id) {
|
|
68
|
+
this.append(scopeKey, { version: 1, type: 'restore', at: new Date().toISOString(), id });
|
|
69
|
+
}
|
|
70
|
+
/** Replays the append-only ledger and returns only currently restorable entries. */
|
|
71
|
+
activeEntries(scopeKey) {
|
|
72
|
+
const file = this.file(scopeKey);
|
|
73
|
+
if (!fs.existsSync(file))
|
|
74
|
+
return [];
|
|
75
|
+
const active = new Map();
|
|
76
|
+
const lines = fs.readFileSync(file, 'utf-8').split(/\r?\n/);
|
|
77
|
+
for (const line of lines) {
|
|
78
|
+
if (!line.trim())
|
|
79
|
+
continue;
|
|
80
|
+
try {
|
|
81
|
+
const event = JSON.parse(line);
|
|
82
|
+
if (event.version !== 1)
|
|
83
|
+
continue;
|
|
84
|
+
if (event.type === 'restore') {
|
|
85
|
+
active.delete(String(event.id || ''));
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (event.type !== 'fold' || !event.entry || typeof event.entry !== 'object')
|
|
89
|
+
continue;
|
|
90
|
+
const candidate = event.entry;
|
|
91
|
+
if (!candidate.id || !Array.isArray(candidate.messages) || typeof candidate.summary !== 'string')
|
|
92
|
+
continue;
|
|
93
|
+
active.set(candidate.id, {
|
|
94
|
+
...candidate,
|
|
95
|
+
messages: candidate.messages.map(message => ({ ...message })),
|
|
96
|
+
foldedEntries: Math.max(0, Number(candidate.foldedEntries) || candidate.messages.length),
|
|
97
|
+
foldedChars: Math.max(0, Number(candidate.foldedChars) || 0),
|
|
98
|
+
model: String(candidate.model || 'unknown'),
|
|
99
|
+
fallback: Boolean(candidate.fallback),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// A partial/corrupt line must not hide earlier valid append-only events.
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return [...active.values()];
|
|
107
|
+
}
|
|
108
|
+
/** Includes restored/tombstoned ids so a restart never reuses an archive id. */
|
|
109
|
+
maxNumericId(scopeKey) {
|
|
110
|
+
const file = this.file(scopeKey);
|
|
111
|
+
if (!fs.existsSync(file))
|
|
112
|
+
return 0;
|
|
113
|
+
let max = 0;
|
|
114
|
+
for (const line of fs.readFileSync(file, 'utf-8').split(/\r?\n/)) {
|
|
115
|
+
if (!line.trim())
|
|
116
|
+
continue;
|
|
117
|
+
try {
|
|
118
|
+
const event = JSON.parse(line);
|
|
119
|
+
const entry = event.entry && typeof event.entry === 'object' ? event.entry : null;
|
|
120
|
+
const id = String(entry?.id || event.id || '');
|
|
121
|
+
max = Math.max(max, Number(id.replace(/^ctx-cache-/, '')) || 0);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// Ignore only the malformed line; valid earlier ids remain authoritative.
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return max;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
exports.CompressionHistoryArchive = CompressionHistoryArchive;
|
|
131
|
+
//# sourceMappingURL=compressionHistoryArchive.js.map
|
package/dist/core/config.js
CHANGED
|
@@ -913,6 +913,7 @@ function defaultConfig() {
|
|
|
913
913
|
keep_recent_messages: { _description: "Keep recent messages", _type: "integer", value: 10 },
|
|
914
914
|
preserve_recent_messages: { _description: "dev-0.3.8 protected recent-message zone for context_history_manage (0 disables)", _type: "integer", value: 5 },
|
|
915
915
|
compression_cache_max: { _description: "dev-0.3.8 max folded-segment cache entries retained for restore/search", _type: "integer", value: 8 },
|
|
916
|
+
compression_archive_enabled: { _description: "dev-0.3.9 append-only cold archive for folded segments evicted from the hot cache", _type: "boolean", value: true },
|
|
916
917
|
structured_context_v2: { _description: "dev-0.3.0 structured context v2 (orchestrator + fixed order + snapshot)", _type: "boolean", value: true },
|
|
917
918
|
build_history_persistence: { _description: "dev-0.3.0 append-only Build History persistence", _type: "boolean", value: true },
|
|
918
919
|
branch_log_v2: { _description: "dev-0.3.0 branch long-log v2 (epoch summaries)", _type: "boolean", value: true },
|
|
@@ -198,7 +198,7 @@ export declare class ConversationKernel {
|
|
|
198
198
|
setWorkRunExpanded(target: ConversationTargetInput, runId: string, expanded: boolean): boolean;
|
|
199
199
|
setInputMode(target: ConversationTargetInput, mode: string): 'guide' | 'next';
|
|
200
200
|
setMode(target: ConversationTargetInput, mode: AgentMode): AgentMode;
|
|
201
|
-
toggleGoalPause(target: ConversationTargetInput): boolean
|
|
201
|
+
toggleGoalPause(target: ConversationTargetInput): Promise<boolean>;
|
|
202
202
|
clearGoal(target: ConversationTargetInput): boolean;
|
|
203
203
|
updateSetting(section: string, key: string, value: unknown): void;
|
|
204
204
|
runtimeState(target: ConversationTargetInput): ConversationRuntimeState | null;
|
|
@@ -212,6 +212,7 @@ export declare class ConversationKernel {
|
|
|
212
212
|
private processTimeoutMs;
|
|
213
213
|
private runtime;
|
|
214
214
|
private scheduleGoalContinuation;
|
|
215
|
+
private startGoalDrivenBuild;
|
|
215
216
|
private createRunner;
|
|
216
217
|
private enqueueRootInboxWake;
|
|
217
218
|
private applyOptions;
|
|
@@ -333,11 +333,26 @@ class ConversationKernel {
|
|
|
333
333
|
runtime.options.mode = mode;
|
|
334
334
|
return runner.mode;
|
|
335
335
|
}
|
|
336
|
-
toggleGoalPause(target) {
|
|
336
|
+
async toggleGoalPause(target) {
|
|
337
337
|
const normalized = this.normalizeTarget(target);
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
338
|
+
let runtime = this.findRuntime(normalized);
|
|
339
|
+
if (!runtime) {
|
|
340
|
+
const runner = this.createRunner(normalized);
|
|
341
|
+
runtime = this.runtime(normalized, {
|
|
342
|
+
mode: runner.mode,
|
|
343
|
+
model: runner.model,
|
|
344
|
+
intelligence: runner.intelligence,
|
|
345
|
+
inputMode: runner.inputMode,
|
|
346
|
+
engine: runner.engine,
|
|
347
|
+
}, runner);
|
|
348
|
+
}
|
|
349
|
+
const wasPaused = runtime.runner.isGoalPaused();
|
|
350
|
+
const hadGoal = !!runtime.runner.goal;
|
|
351
|
+
const paused = runtime.runner.toggleGoalPause();
|
|
352
|
+
if (hadGoal && wasPaused && !paused)
|
|
353
|
+
this.startGoalDrivenBuild(runtime);
|
|
354
|
+
this.mirrorHostIfTargetActive(runtime);
|
|
355
|
+
return paused;
|
|
341
356
|
}
|
|
342
357
|
clearGoal(target) {
|
|
343
358
|
const normalized = this.normalizeTarget(target);
|
|
@@ -401,6 +416,7 @@ class ConversationKernel {
|
|
|
401
416
|
}
|
|
402
417
|
runtime.stopRequestedRunId = runtime.runId;
|
|
403
418
|
runtime.forceStopArmedRunId = runtime.runId;
|
|
419
|
+
runtime.runner.pauseGoalForUserInterrupt();
|
|
404
420
|
this.retainUnconsumedKernelMessages(runtime);
|
|
405
421
|
this.deferOutstandingGuides(runtime);
|
|
406
422
|
const checkpointed = this.checkpoint(runtime.target).checkpointed;
|
|
@@ -610,7 +626,7 @@ class ConversationKernel {
|
|
|
610
626
|
return 0;
|
|
611
627
|
return Math.max(1000, Math.floor(raw));
|
|
612
628
|
}
|
|
613
|
-
runtime(target, options) {
|
|
629
|
+
runtime(target, options, runnerOverride) {
|
|
614
630
|
const existing = this.findRuntime(target);
|
|
615
631
|
if (existing) {
|
|
616
632
|
this.ensureRuntimeMetadata(existing, target);
|
|
@@ -618,7 +634,7 @@ class ConversationKernel {
|
|
|
618
634
|
return existing;
|
|
619
635
|
}
|
|
620
636
|
const id = target.conversationId;
|
|
621
|
-
const runner = this.createRunner(target);
|
|
637
|
+
const runner = runnerOverride || this.createRunner(target);
|
|
622
638
|
runner.setAutomationManager(this.automation);
|
|
623
639
|
this.applyOptions(runner, options);
|
|
624
640
|
const runtime = {
|
|
@@ -732,8 +748,23 @@ class ConversationKernel {
|
|
|
732
748
|
});
|
|
733
749
|
}, 250);
|
|
734
750
|
}
|
|
751
|
+
startGoalDrivenBuild(runtime) {
|
|
752
|
+
if (runtime.goalContinuationTimer) {
|
|
753
|
+
clearTimeout(runtime.goalContinuationTimer);
|
|
754
|
+
runtime.goalContinuationTimer = undefined;
|
|
755
|
+
}
|
|
756
|
+
const message = runtime.runner.claimGoalContinuationMessage({ force: true });
|
|
757
|
+
if (!message)
|
|
758
|
+
return;
|
|
759
|
+
void this.prompt(message, runtime.target, { ...runtime.options, mode: 'goal' }, 'followUp').catch(() => {
|
|
760
|
+
// Agent.process records and publishes the Build error.
|
|
761
|
+
});
|
|
762
|
+
}
|
|
735
763
|
createRunner(target) {
|
|
736
|
-
const runner = this.lifecycle.createRunner?.(target) || new agent_1.Agent(this.root, {
|
|
764
|
+
const runner = this.lifecycle.createRunner?.(target) || new agent_1.Agent(this.root, {
|
|
765
|
+
actorId: this.host.runtimeActorId,
|
|
766
|
+
runtimeLifecycleRole: this.host.runtimeLifecycleRole,
|
|
767
|
+
});
|
|
737
768
|
if (target.workspace) {
|
|
738
769
|
runner.workspace.current = {
|
|
739
770
|
id: target.workspace.id,
|
|
@@ -94,11 +94,20 @@ class ElectronBrowserUseHost {
|
|
|
94
94
|
evaluateFixed: async (script, signal) => await raceWithAbort(contents.executeJavaScriptInIsolatedWorld(BROWSER_USE_WORLD_ID, [{ code: script }], true), signal),
|
|
95
95
|
clickAt: async (x, y, signal) => {
|
|
96
96
|
throwIfAborted(signal);
|
|
97
|
+
// A webview guest can retain the DOM focus while its embedder is still
|
|
98
|
+
// committing a workspace/tab transition. Focus both sides and yield
|
|
99
|
+
// between native input events so Chromium receives a real click rather
|
|
100
|
+
// than a synchronously queued sequence that can be dropped by the guest.
|
|
101
|
+
contents.hostWebContents?.focus();
|
|
97
102
|
contents.focus();
|
|
103
|
+
await abortableDelay(10, signal);
|
|
98
104
|
const point = { x: Math.max(0, Math.round(x)), y: Math.max(0, Math.round(y)) };
|
|
99
105
|
contents.sendInputEvent({ type: 'mouseMove', ...point });
|
|
106
|
+
await abortableDelay(10, signal);
|
|
100
107
|
contents.sendInputEvent({ type: 'mouseDown', button: 'left', clickCount: 1, ...point });
|
|
108
|
+
await abortableDelay(10, signal);
|
|
101
109
|
contents.sendInputEvent({ type: 'mouseUp', button: 'left', clickCount: 1, ...point });
|
|
110
|
+
await abortableDelay(10, signal);
|
|
102
111
|
},
|
|
103
112
|
replaceFocusedText: async (text, signal) => {
|
|
104
113
|
throwIfAborted(signal);
|
|
@@ -247,7 +247,7 @@ class ElectronUtilityRuntimePool {
|
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
249
|
async toggleGoalPause(target) {
|
|
250
|
-
const entry = await this.
|
|
250
|
+
const entry = await this.acquire((0, conversationTarget_1.normalizeConversationTarget)(target));
|
|
251
251
|
if (!entry?.client.toggleGoalPause)
|
|
252
252
|
return null;
|
|
253
253
|
try {
|
|
@@ -630,12 +630,11 @@ class ElectronUtilityRuntimePool {
|
|
|
630
630
|
}
|
|
631
631
|
const finalized = !!kernelForce && kernelForce.action === 'force';
|
|
632
632
|
const checkpointed = finalized ? (kernelForce.checkpointed || intent.checkpointed) : intent.checkpointed;
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
}
|
|
633
|
+
// The acknowledgement only gives the kernel a chance to persist its
|
|
634
|
+
// force_interrupted settlement. A second Stop still owns a process-level
|
|
635
|
+
// termination boundary and must leave the target runtime down until the
|
|
636
|
+
// next prompt, even when the worker acknowledged the force request.
|
|
637
|
+
await entry.client.forceStop();
|
|
639
638
|
entry.lastSnapshot = null;
|
|
640
639
|
entry.workEvents = [];
|
|
641
640
|
entry.lastRunId = '';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type RuntimeLifecycleRole = 'main' | 'utility' | 'wsl';
|
|
2
|
+
export interface RuntimeLifecycleState {
|
|
3
|
+
role: RuntimeLifecycleRole;
|
|
4
|
+
ownerId: string;
|
|
5
|
+
pid: number;
|
|
6
|
+
startedAt: string;
|
|
7
|
+
previousOwnerAlive: boolean;
|
|
8
|
+
unexpectedExit: boolean;
|
|
9
|
+
active: true;
|
|
10
|
+
}
|
|
11
|
+
export declare function isRuntimeProcessAlive(pid: number): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Claim this process/role as the current runtime owner.
|
|
14
|
+
*
|
|
15
|
+
* A frontend cold runner in the same process gets the same in-memory claim and
|
|
16
|
+
* therefore does not look like a restart. A genuinely new process sees the
|
|
17
|
+
* previous active claim and can pause orphaned Goal/Flow state once.
|
|
18
|
+
*/
|
|
19
|
+
export declare function beginRuntimeLifecycle(root: string, role?: RuntimeLifecycleRole): RuntimeLifecycleState;
|
|
20
|
+
/** Mark only this owner clean; a hard kill leaves the active marker intact. */
|
|
21
|
+
export declare function markRuntimeLifecycleClean(root: string, role?: RuntimeLifecycleRole): void;
|
|
22
|
+
export declare function runtimeLifecycleState(root: string, role?: RuntimeLifecycleRole): RuntimeLifecycleState | null;
|
|
23
|
+
//# sourceMappingURL=runtimeLifecycle.d.ts.map
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.isRuntimeProcessAlive = isRuntimeProcessAlive;
|
|
37
|
+
exports.beginRuntimeLifecycle = beginRuntimeLifecycle;
|
|
38
|
+
exports.markRuntimeLifecycleClean = markRuntimeLifecycleClean;
|
|
39
|
+
exports.runtimeLifecycleState = runtimeLifecycleState;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const crypto_1 = require("crypto");
|
|
43
|
+
const processStates = new Map();
|
|
44
|
+
function stateKey(root, role) {
|
|
45
|
+
return `${path.resolve(root)}\u0000${role}`;
|
|
46
|
+
}
|
|
47
|
+
function statePath(root, role, ownerId = '') {
|
|
48
|
+
return path.join(root, '.newmark-runtime', ownerId ? `lifecycle-${role}-${ownerId}.json` : `lifecycle-${role}.json`);
|
|
49
|
+
}
|
|
50
|
+
function readState(file) {
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
53
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function isRuntimeProcessAlive(pid) {
|
|
60
|
+
const candidate = Math.floor(Number(pid) || 0);
|
|
61
|
+
if (candidate <= 0)
|
|
62
|
+
return false;
|
|
63
|
+
try {
|
|
64
|
+
process.kill(candidate, 0);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function writeState(root, role, state) {
|
|
72
|
+
const ownerId = typeof state.ownerId === 'string' ? state.ownerId : '';
|
|
73
|
+
const file = statePath(root, role, ownerId);
|
|
74
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
75
|
+
const temporary = `${file}.${process.pid}.${(0, crypto_1.randomUUID)()}.tmp`;
|
|
76
|
+
fs.writeFileSync(temporary, JSON.stringify(state, null, 2), 'utf-8');
|
|
77
|
+
fs.renameSync(temporary, file);
|
|
78
|
+
}
|
|
79
|
+
function readActiveStates(root, role) {
|
|
80
|
+
const directory = path.join(root, '.newmark-runtime');
|
|
81
|
+
try {
|
|
82
|
+
const prefix = `lifecycle-${role}`;
|
|
83
|
+
return fs.readdirSync(directory)
|
|
84
|
+
.filter(file => file.startsWith(prefix) && file.endsWith('.json'))
|
|
85
|
+
.map(file => readState(path.join(directory, file)))
|
|
86
|
+
.filter((state) => !!state && state.active === true);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Claim this process/role as the current runtime owner.
|
|
94
|
+
*
|
|
95
|
+
* A frontend cold runner in the same process gets the same in-memory claim and
|
|
96
|
+
* therefore does not look like a restart. A genuinely new process sees the
|
|
97
|
+
* previous active claim and can pause orphaned Goal/Flow state once.
|
|
98
|
+
*/
|
|
99
|
+
function beginRuntimeLifecycle(root, role = 'main') {
|
|
100
|
+
const key = stateKey(root, role);
|
|
101
|
+
const existingProcessState = processStates.get(key);
|
|
102
|
+
if (existingProcessState)
|
|
103
|
+
return existingProcessState;
|
|
104
|
+
const previousStates = readActiveStates(root, role);
|
|
105
|
+
const previousOwnerAlive = previousStates.some(previous => isRuntimeProcessAlive(Number(previous.pid)));
|
|
106
|
+
const state = {
|
|
107
|
+
role,
|
|
108
|
+
ownerId: (0, crypto_1.randomUUID)(),
|
|
109
|
+
pid: process.pid,
|
|
110
|
+
startedAt: new Date().toISOString(),
|
|
111
|
+
previousOwnerAlive,
|
|
112
|
+
unexpectedExit: previousStates.length > 0 && !previousOwnerAlive,
|
|
113
|
+
active: true,
|
|
114
|
+
};
|
|
115
|
+
try {
|
|
116
|
+
writeState(root, role, state);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Recovery remains conservative when the marker cannot be written. The
|
|
120
|
+
// persisted WorkRun timestamp still protects a live backend cold read.
|
|
121
|
+
}
|
|
122
|
+
processStates.set(key, state);
|
|
123
|
+
return state;
|
|
124
|
+
}
|
|
125
|
+
/** Mark only this owner clean; a hard kill leaves the active marker intact. */
|
|
126
|
+
function markRuntimeLifecycleClean(root, role = 'main') {
|
|
127
|
+
const key = stateKey(root, role);
|
|
128
|
+
const current = processStates.get(key);
|
|
129
|
+
if (!current)
|
|
130
|
+
return;
|
|
131
|
+
try {
|
|
132
|
+
writeState(root, role, {
|
|
133
|
+
...current,
|
|
134
|
+
active: false,
|
|
135
|
+
cleanExitAt: new Date().toISOString(),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// Leaving the marker active is safer than claiming a clean exit after a
|
|
140
|
+
// failed durable write.
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function runtimeLifecycleState(root, role = 'main') {
|
|
144
|
+
return processStates.get(stateKey(root, role)) || null;
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=runtimeLifecycle.js.map
|
package/dist/core/types.d.ts
CHANGED
|
@@ -129,6 +129,9 @@ export interface ConversationWorkRun {
|
|
|
129
129
|
runId: string;
|
|
130
130
|
target: ConversationTarget;
|
|
131
131
|
runtimeKey: string;
|
|
132
|
+
runtimeOwnerId?: string;
|
|
133
|
+
runtimeOwnerPid?: number;
|
|
134
|
+
runtimeLifecycleRole?: 'main' | 'utility' | 'wsl';
|
|
132
135
|
status: ConversationWorkRunStatus;
|
|
133
136
|
startedAt: string;
|
|
134
137
|
endedAt?: string;
|
|
@@ -262,7 +262,7 @@ class WslAgentRuntimePool {
|
|
|
262
262
|
}
|
|
263
263
|
async toggleGoalPause(target) {
|
|
264
264
|
const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
|
|
265
|
-
const entry = await this.
|
|
265
|
+
const entry = await this.acquire(normalized);
|
|
266
266
|
if (!entry?.client.toggleGoalPause)
|
|
267
267
|
return null;
|
|
268
268
|
try {
|
|
@@ -616,15 +616,14 @@ class WslAgentRuntimePool {
|
|
|
616
616
|
}
|
|
617
617
|
const finalized = !!kernelForce && kernelForce.action === 'force';
|
|
618
618
|
const checkpointed = finalized ? (kernelForce.checkpointed || intent.checkpointed) : intent.checkpointed;
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
}
|
|
619
|
+
// The acknowledgement only gives the kernel a chance to persist its
|
|
620
|
+
// force_interrupted settlement. A second Stop still owns a process-group
|
|
621
|
+
// termination boundary and must leave the target runtime down until the
|
|
622
|
+
// next prompt, even when the worker acknowledged the force request.
|
|
623
|
+
if (entry.client.forceStopRuntimeGroup)
|
|
624
|
+
await entry.client.forceStopRuntimeGroup();
|
|
625
|
+
else
|
|
626
|
+
await entry.client.forceRestartRuntimeGroup();
|
|
628
627
|
entry.lastSnapshot = null;
|
|
629
628
|
entry.workEvents = [];
|
|
630
629
|
entry.lastRunId = '';
|
package/dist/main.js
CHANGED
|
@@ -70,6 +70,7 @@ const wslAgentRuntimePool_1 = require("./core/wslAgentRuntimePool");
|
|
|
70
70
|
const utilityHostToolRouter_1 = require("./core/utilityHostToolRouter");
|
|
71
71
|
const startupPrewarm_1 = require("./core/startupPrewarm");
|
|
72
72
|
const runtimeShutdown_1 = require("./core/runtimeShutdown");
|
|
73
|
+
const runtimeLifecycle_1 = require("./core/runtimeLifecycle");
|
|
73
74
|
const compat_1 = require("./core/compat");
|
|
74
75
|
const mcpManager_1 = require("./core/mcpManager");
|
|
75
76
|
const APP_NAME = 'Newmark Agent';
|
|
@@ -2106,6 +2107,15 @@ else {
|
|
|
2106
2107
|
}
|
|
2107
2108
|
electron_1.app.quit();
|
|
2108
2109
|
};
|
|
2110
|
+
const hasUnsettledGoalOrFlow = () => {
|
|
2111
|
+
const goalRunning = !!agent?.goal && !agent.goal.paused;
|
|
2112
|
+
const flowRunning = Array.from(activeFlowsByRuntimeKey.values()).some(state => !!state.abortController);
|
|
2113
|
+
return goalRunning || flowRunning;
|
|
2114
|
+
};
|
|
2115
|
+
const markMainLifecycleCleanIfSafe = () => {
|
|
2116
|
+
if (!hasUnsettledGoalOrFlow())
|
|
2117
|
+
(0, runtimeLifecycle_1.markRuntimeLifecycleClean)(root, 'main');
|
|
2118
|
+
};
|
|
2109
2119
|
electron_1.app.on('will-quit', event => {
|
|
2110
2120
|
if (_forceQuit)
|
|
2111
2121
|
armForcedExitDeadline('will-quit');
|
|
@@ -2131,6 +2141,7 @@ else {
|
|
|
2131
2141
|
throw error;
|
|
2132
2142
|
})
|
|
2133
2143
|
: undefined;
|
|
2144
|
+
let shutdownFailed = false;
|
|
2134
2145
|
void (0, runtimeShutdown_1.runRuntimeShutdownBarrier)({
|
|
2135
2146
|
operations: [
|
|
2136
2147
|
legacyStop,
|
|
@@ -2139,9 +2150,12 @@ else {
|
|
|
2139
2150
|
],
|
|
2140
2151
|
shutdownHelpers: async () => await (0, electronUtilityAgentClient_1.shutdownWindowsProcessHelpers)(2_000),
|
|
2141
2152
|
}).catch(error => {
|
|
2153
|
+
shutdownFailed = true;
|
|
2142
2154
|
console.error('[Newmark] Runtime shutdown cleanup failed:', error instanceof Error ? error.message : String(error));
|
|
2143
2155
|
}).finally(() => {
|
|
2144
2156
|
appExitCleanupComplete = true;
|
|
2157
|
+
if (!shutdownFailed)
|
|
2158
|
+
markMainLifecycleCleanIfSafe();
|
|
2145
2159
|
electron_1.app.quit();
|
|
2146
2160
|
});
|
|
2147
2161
|
}
|
|
@@ -2170,6 +2184,7 @@ else {
|
|
|
2170
2184
|
sidecarProcess = null;
|
|
2171
2185
|
}
|
|
2172
2186
|
(0, terminalTakeover_1.shutdownTerminalTakeoverSessions)('app-exit');
|
|
2187
|
+
markMainLifecycleCleanIfSafe();
|
|
2173
2188
|
if (forcedExitTimer) {
|
|
2174
2189
|
clearTimeout(forcedExitTimer);
|
|
2175
2190
|
forcedExitTimer = null;
|
package/dist/tools/index.js
CHANGED
|
@@ -374,13 +374,16 @@ class ToolExecutor {
|
|
|
374
374
|
t('linked_plan', 'Read or update the current conversation linked Markdown plan. Update requires the current expected_revision.', { action: { type: 'string', enum: ['get', 'update'] }, markdown: { type: 'string' }, expected_revision: { type: 'number' } }, ['action']),
|
|
375
375
|
t('build_history_query', 'Read the concrete public work details of one historical Build Block. The prompt already exposes user input, final summary, and completion status; call this read-only tool only when the user asks what specifically happened. Select by newest-to-oldest history_index, or by run_id returned from an earlier query.', { history_index: { type: 'number', minimum: 1, description: '1-based historical Build Block index from the request ledger; 1 is the newest previous task.' }, run_id: { type: 'string', description: 'Exact run id returned by an earlier build_history_query result.' }, max_events: { type: 'number', minimum: 1, maximum: 200, description: 'Maximum trailing public work events; defaults to 80.' } }, []),
|
|
376
376
|
t('context_compress', 'Actively compress the LLM context history for this conversation. This collapses older history entries into a concise summary while preserving the recent tail, which reduces context tokens and cost. IMPORTANT: it affects only the LLM context (what the model sees); the displayed conversation history shown to the user is never altered. Call this when the conversation is long, token pressure is high, or you judge that older turns are no longer needed in full. Idempotent and safe: repeated calls produce incremental summaries.', { keep_recent: { type: 'number', minimum: 2, maximum: 60, description: 'Recent message count to keep uncompressed at the tail. Defaults to the configured keep_recent_messages.' }, force: { type: 'boolean', description: 'Compress even if the context is not yet over the automatic threshold. Defaults to false.' } }, []),
|
|
377
|
-
t('context_history_manage', 'Manage the LLM context history for this conversation without affecting the displayed conversation history. This is the active context-management surface.
|
|
378
|
-
action: { type: 'string', enum: ['list', 'remove', 'summarize', 'restore', 'search', 'status'], description: 'list
|
|
377
|
+
t('context_history_manage', 'Manage the LLM context history for this conversation without affecting the displayed conversation history. This is the active context-management surface. The hot cache stays bounded; evicted folded segments remain in a conversation-isolated append-only cold archive and are loaded only by explicit search/read/restore calls. Actions: list returns a bounded index of current context entries; remove deletes one current entry; summarize folds a contiguous current range; restore reinserts a folded segment when its summary marker is still present; search finds matching hot or archived segments; read returns one bounded segment without injecting the whole archive; status reports budgets, hot cache, cold archive, and the protected recent zone. The recent context tail and last user message are protected from remove/summarize unless dangerous is true.', {
|
|
378
|
+
action: { type: 'string', enum: ['list', 'remove', 'summarize', 'restore', 'search', 'read', 'status'], description: 'list current entries; remove one; summarize a range; restore by restore_id; search hot/cold folded segments; read one bounded folded segment; status report context budgets and storage.' },
|
|
379
379
|
position: { type: 'number', minimum: 0, description: '0-based context entry index for remove, or the start of the range for summarize.' },
|
|
380
380
|
to: { type: 'number', minimum: 0, description: '0-based inclusive end of the range for summarize. Defaults to position.' },
|
|
381
|
-
limit: { type: 'number', minimum:
|
|
381
|
+
limit: { type: 'number', minimum: 1, maximum: 400, description: 'Maximum context entries/messages/matches to return. Current-history list still keeps a minimum page of 5.' },
|
|
382
382
|
restore_id: { type: 'string', description: 'Cache id of a folded segment (from search or status) to restore into context.' },
|
|
383
383
|
query: { type: 'string', description: 'Case-insensitive text to search for across cached folded segments and their summaries.' },
|
|
384
|
+
offset: { type: 'number', minimum: 0, description: 'Message offset for read pagination.' },
|
|
385
|
+
content_offset: { type: 'number', minimum: 0, description: 'Character offset within the first read message, used with nextContentOffset when one message exceeds max_chars.' },
|
|
386
|
+
max_chars: { type: 'number', minimum: 1000, maximum: 60000, description: 'Maximum message-content characters returned by read (default 12000).' },
|
|
384
387
|
dangerous: { type: 'boolean', description: 'Set true to override the protected recent-message zone and allow removing/summarizing entries that include the recent context tail or the last user message.' },
|
|
385
388
|
}, ['action']),
|
|
386
389
|
t('question', 'Ask user a multiple-choice question', { questions: { type: 'array' } }, ['questions']),
|
|
@@ -41,7 +41,7 @@ exports.NATIVE_TOOL_CATALOG = [
|
|
|
41
41
|
{ name: 'linked_plan', label: 'Linked plan', description: 'Read or conservatively update the conversation-linked Markdown plan.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
42
42
|
{ name: 'build_history_query', label: 'Build history query', description: 'Read concrete public work details for one historical Build Block.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
43
43
|
{ name: 'context_compress', label: 'Context compress', description: 'Actively compress the LLM context history, leaving the displayed conversation history unchanged.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
44
|
-
{ name: 'context_history_manage', label: 'Context history manage', description: '
|
|
44
|
+
{ name: 'context_history_manage', label: 'Context history manage', description: 'Inspect, search, restore, or fold LLM context history without touching the displayed conversation history.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
45
45
|
{ name: 'question', label: 'Ask question', description: 'Ask the user for structured option feedback.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
46
46
|
{ name: 'skill_download', label: 'Skill download', description: 'Download and install a skill.', category: 'agent', defaultEnabled: true },
|
|
47
47
|
{ name: 'skill', label: 'Skill', description: 'Search enabled skill metadata or load one skill body on demand.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
package/dist/ui/index.html
CHANGED
|
@@ -13682,7 +13682,6 @@ function activateSubmittedGoal(goalObjective) {
|
|
|
13682
13682
|
if (!objective) return '';
|
|
13683
13683
|
state.goalText = objective;
|
|
13684
13684
|
state.goalVisible = true;
|
|
13685
|
-
state.goalPaused = false;
|
|
13686
13685
|
window.renderInputStack();
|
|
13687
13686
|
return objective;
|
|
13688
13687
|
}
|