@myagentroam/node 0.9.130 → 0.9.132
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/connector.js +52 -7
- package/dist/marmgr.js +5 -0
- package/dist/marnodes.js +4 -0
- package/dist/marscp.js +4 -0
- package/dist/marssh.js +4 -0
- package/dist/runner/abstract-runner.js +17 -4
- package/dist/runner/claude/managed-run-controller.js +1 -1
- package/dist/runner/codex/managed-run-controller.js +22 -1
- package/dist/runner/mar-agent/managed-run-controller.js +1 -1
- package/dist/runner/opencode/managed-run-controller.js +4 -1
- package/dist/service/goal-runtime.js +14 -16
- package/dist/service/terminal-service.js +6 -3
- package/dist/terminal.js +85 -7
- package/package.json +10 -5
package/dist/connector.js
CHANGED
|
@@ -263,6 +263,7 @@ export class NodeConnector {
|
|
|
263
263
|
goalSubmissionAttempts = new Map();
|
|
264
264
|
goalContinuationEpochs = new Map();
|
|
265
265
|
goalExecutionProgress = new Set();
|
|
266
|
+
nativeGoalBindings = new Map();
|
|
266
267
|
sessionScheduleService;
|
|
267
268
|
goalMcpServer = new GoalMcpServer(this.goalRuntime, (sessionId) => {
|
|
268
269
|
const goal = this.goalRuntime.get(sessionId);
|
|
@@ -270,6 +271,7 @@ export class NodeConnector {
|
|
|
270
271
|
if (goal === undefined || session === undefined)
|
|
271
272
|
return;
|
|
272
273
|
if (goal.status !== 'active') {
|
|
274
|
+
this.nativeGoalBindings.delete(goal.id);
|
|
273
275
|
this.retireGoalContinuation(sessionId);
|
|
274
276
|
this.continuousRunCoordinator.removeGoalQueue(sessionId);
|
|
275
277
|
}
|
|
@@ -384,11 +386,9 @@ export class NodeConnector {
|
|
|
384
386
|
failed: status === 'FAILED',
|
|
385
387
|
progressed: status !== 'SUCCEEDED' || progressed
|
|
386
388
|
});
|
|
387
|
-
if (disposition === 'blocked' || disposition === '
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
else
|
|
391
|
-
this.invalidateGoalContinuation(pending.sessionId);
|
|
389
|
+
if (disposition === 'blocked' || disposition === 'completed') {
|
|
390
|
+
this.nativeGoalBindings.delete(pending.goal.id);
|
|
391
|
+
this.retireGoalContinuation(pending.sessionId);
|
|
392
392
|
this.continuousRunCoordinator.removeGoalQueue(pending.sessionId);
|
|
393
393
|
const terminalGoal = this.goalRuntime.get(pending.sessionId);
|
|
394
394
|
const session = this.runtime.getAgentSession(pending.sessionId);
|
|
@@ -528,6 +528,7 @@ export class NodeConnector {
|
|
|
528
528
|
promoteSession: (sessionId, nativeSessionId) => this.sessionIdentityService.promote(sessionId, nativeSessionId),
|
|
529
529
|
send: (type, payload) => this.send(type, payload),
|
|
530
530
|
emit: (runId, eventType, payload, status) => this.runEventBridge.emitRun(runId, eventType, payload, status),
|
|
531
|
+
nativeGoalUpdated: (runId, goal) => this.handleNativeGoalUpdate(runId, goal),
|
|
531
532
|
cancel: (runId, threadId, turnId, cwd, status) => this.runnerInteractionService.cancelCodexTurn(runId, threadId, turnId, cwd, status),
|
|
532
533
|
createApproval: (input) => this.runtime.createApproval(input),
|
|
533
534
|
publishApproval: (input) => this.publishApprovalForRun(input),
|
|
@@ -985,6 +986,9 @@ export class NodeConnector {
|
|
|
985
986
|
goalAlreadyActive: true
|
|
986
987
|
};
|
|
987
988
|
if (existing !== undefined) {
|
|
989
|
+
const previousGoal = this.goalRuntime.get(session.id);
|
|
990
|
+
if (previousGoal !== undefined)
|
|
991
|
+
this.nativeGoalBindings.delete(previousGoal.id);
|
|
988
992
|
this.goalRuntime.clear(session.id);
|
|
989
993
|
this.commandStates.clear(session.id, 'goal');
|
|
990
994
|
}
|
|
@@ -1476,7 +1480,8 @@ export class NodeConnector {
|
|
|
1476
1480
|
this.operationRouter.register('node.upgrade.request', (data) => this.upgradeCoordinator.request(data));
|
|
1477
1481
|
this.operationRouter.register('node.upgrade.cancel', () => this.upgradeCoordinator.cancel());
|
|
1478
1482
|
this.operationRouter.register('node.workbench.capabilities', async () => ({
|
|
1479
|
-
manifest: await this.workbenchManifestService.read()
|
|
1483
|
+
manifest: await this.workbenchManifestService.read(),
|
|
1484
|
+
binaryTerminal: true
|
|
1480
1485
|
}));
|
|
1481
1486
|
}
|
|
1482
1487
|
async initializeConversationHosting() {
|
|
@@ -1720,10 +1725,46 @@ export class NodeConnector {
|
|
|
1720
1725
|
const current = this.goalRuntime.get(sessionId);
|
|
1721
1726
|
return ((this.goalContinuationEpochs.get(sessionId) ?? 0) === goal.epoch &&
|
|
1722
1727
|
current?.status === 'active' &&
|
|
1723
|
-
!current.continuationPaused &&
|
|
1724
1728
|
current.id === goal.id &&
|
|
1725
1729
|
current.revision === goal.revision);
|
|
1726
1730
|
}
|
|
1731
|
+
handleNativeGoalUpdate(runId, nativeGoal) {
|
|
1732
|
+
const pending = this.continuousRunCoordinator.pending(runId);
|
|
1733
|
+
if (pending?.goal === undefined || !this.goalGenerationCurrent(pending.sessionId, pending.goal))
|
|
1734
|
+
return;
|
|
1735
|
+
const goal = this.goalRuntime.get(pending.sessionId);
|
|
1736
|
+
if (nativeGoal.status === 'active') {
|
|
1737
|
+
if ((nativeGoal.createdAt + 1) * 1_000 < goal.revisionStartedAt)
|
|
1738
|
+
return;
|
|
1739
|
+
const previous = this.nativeGoalBindings.get(goal.id);
|
|
1740
|
+
if (previous !== undefined &&
|
|
1741
|
+
previous.revision !== goal.revision &&
|
|
1742
|
+
previous.threadId === nativeGoal.threadId &&
|
|
1743
|
+
previous.createdAt === nativeGoal.createdAt &&
|
|
1744
|
+
previous.objective === nativeGoal.objective)
|
|
1745
|
+
return;
|
|
1746
|
+
this.nativeGoalBindings.set(goal.id, {
|
|
1747
|
+
revision: goal.revision,
|
|
1748
|
+
threadId: nativeGoal.threadId,
|
|
1749
|
+
createdAt: nativeGoal.createdAt,
|
|
1750
|
+
objective: nativeGoal.objective
|
|
1751
|
+
});
|
|
1752
|
+
return;
|
|
1753
|
+
}
|
|
1754
|
+
const bound = this.nativeGoalBindings.get(goal.id);
|
|
1755
|
+
if (bound?.revision !== goal.revision ||
|
|
1756
|
+
bound.threadId !== nativeGoal.threadId ||
|
|
1757
|
+
bound.createdAt !== nativeGoal.createdAt ||
|
|
1758
|
+
bound.objective !== nativeGoal.objective)
|
|
1759
|
+
return;
|
|
1760
|
+
this.nativeGoalBindings.delete(goal.id);
|
|
1761
|
+
const completed = this.goalRuntime.update(pending.sessionId, goal.id, goal.revision, 'completed');
|
|
1762
|
+
this.retireGoalContinuation(pending.sessionId);
|
|
1763
|
+
this.continuousRunCoordinator.removeGoalQueue(pending.sessionId);
|
|
1764
|
+
const session = this.runtime.getAgentSession(pending.sessionId);
|
|
1765
|
+
if (session !== undefined)
|
|
1766
|
+
this.publishGoalState(session, completed);
|
|
1767
|
+
}
|
|
1727
1768
|
beginGoalSubmission(sessionId, continuationEpoch = this.goalContinuationEpochs.get(sessionId) ?? 0) {
|
|
1728
1769
|
const attempt = { sessionId, epoch: continuationEpoch };
|
|
1729
1770
|
const attempts = this.goalSubmissionAttempts.get(sessionId) ?? new Set();
|
|
@@ -1814,6 +1855,7 @@ export class NodeConnector {
|
|
|
1814
1855
|
this.clearGoalRetry(attempt.sessionId);
|
|
1815
1856
|
if (failedAttempts > 3) {
|
|
1816
1857
|
const blocked = this.goalRuntime.blockAfterRuntimeFailure(attempt.sessionId, input.goal.id, input.goal.revision);
|
|
1858
|
+
this.nativeGoalBindings.delete(input.goal.id);
|
|
1817
1859
|
this.continuousRunCoordinator.removeGoalQueue(attempt.sessionId);
|
|
1818
1860
|
this.goalContinuationEpochs.delete(attempt.sessionId);
|
|
1819
1861
|
this.finishIdleGoalRun(attempt.sessionId, 'FAILED');
|
|
@@ -1861,6 +1903,9 @@ export class NodeConnector {
|
|
|
1861
1903
|
}
|
|
1862
1904
|
clearGoalState(sessionId, status) {
|
|
1863
1905
|
const goalExit = this.goalRuntime.exitSummary(sessionId, status);
|
|
1906
|
+
const goal = this.goalRuntime.get(sessionId);
|
|
1907
|
+
if (goal !== undefined)
|
|
1908
|
+
this.nativeGoalBindings.delete(goal.id);
|
|
1864
1909
|
this.retireGoalContinuation(sessionId);
|
|
1865
1910
|
const removed = this.goalRuntime.clear(sessionId);
|
|
1866
1911
|
this.continuousRunCoordinator.removeGoalQueue(sessionId);
|
package/dist/marmgr.js
ADDED
package/dist/marnodes.js
ADDED
package/dist/marscp.js
ADDED
package/dist/marssh.js
ADDED
|
@@ -166,6 +166,15 @@ export class AbstractRunner {
|
|
|
166
166
|
function resolvedMcpServers(raw, secrets) {
|
|
167
167
|
if (!Array.isArray(raw))
|
|
168
168
|
return [];
|
|
169
|
+
const marmgrEnvironment = {
|
|
170
|
+
...(secrets['MAR_MGR_SERVER_URL'] === undefined
|
|
171
|
+
? {}
|
|
172
|
+
: { MAR_MGR_SERVER_URL: secrets['MAR_MGR_SERVER_URL'] }),
|
|
173
|
+
...(secrets['MAR_MGR_USER_TOKEN'] === undefined
|
|
174
|
+
? {}
|
|
175
|
+
: { MAR_MGR_USER_TOKEN: secrets['MAR_MGR_USER_TOKEN'] }),
|
|
176
|
+
MAR_MGR_CONTEXT_REQUIRED: '1'
|
|
177
|
+
};
|
|
169
178
|
const result = [];
|
|
170
179
|
for (const value of raw) {
|
|
171
180
|
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
@@ -191,17 +200,20 @@ function resolvedMcpServers(raw, secrets) {
|
|
|
191
200
|
environment: record['__marGoal'] === true
|
|
192
201
|
? {
|
|
193
202
|
MAR_GOAL_MCP_ENDPOINT: record['endpoint'],
|
|
194
|
-
MAR_GOAL_MCP_TOKEN: secrets[record['secretName']]
|
|
203
|
+
MAR_GOAL_MCP_TOKEN: secrets[record['secretName']],
|
|
204
|
+
...marmgrEnvironment
|
|
195
205
|
}
|
|
196
206
|
: record['__marScheduled'] === true
|
|
197
207
|
? {
|
|
198
208
|
MAR_SCHEDULE_MCP_ENDPOINT: record['endpoint'],
|
|
199
209
|
MAR_SCHEDULE_MCP_TOKEN: secrets[record['secretName']],
|
|
200
|
-
MAR_SCHEDULE_MCP_READ_ONLY: record['readOnly'] === true ? '1' : '0'
|
|
210
|
+
MAR_SCHEDULE_MCP_READ_ONLY: record['readOnly'] === true ? '1' : '0',
|
|
211
|
+
...marmgrEnvironment
|
|
201
212
|
}
|
|
202
213
|
: {
|
|
203
214
|
MAR_MAIL_MCP_ENDPOINT: record['endpoint'],
|
|
204
|
-
MAR_MAIL_MCP_TOKEN: secrets[record['secretName']]
|
|
215
|
+
MAR_MAIL_MCP_TOKEN: secrets[record['secretName']],
|
|
216
|
+
...marmgrEnvironment
|
|
205
217
|
}
|
|
206
218
|
});
|
|
207
219
|
continue;
|
|
@@ -241,7 +253,8 @@ function resolvedMcpServers(raw, secrets) {
|
|
|
241
253
|
],
|
|
242
254
|
environment: {
|
|
243
255
|
...runtimeEnvironment,
|
|
244
|
-
...configuredEnvironment
|
|
256
|
+
...configuredEnvironment,
|
|
257
|
+
...marmgrEnvironment
|
|
245
258
|
}
|
|
246
259
|
});
|
|
247
260
|
}
|
|
@@ -170,7 +170,7 @@ export class ClaudeManagedRunController {
|
|
|
170
170
|
...(typeof payload.model === 'string' ? { model: payload.model } : {}),
|
|
171
171
|
...(typeof payload.effort === 'string' ? { effort: payload.effort } : {}),
|
|
172
172
|
...(typeof payload.access === 'string' ? { access: payload.access } : {}),
|
|
173
|
-
environment: secretEnvironment,
|
|
173
|
+
environment: { ...secretEnvironment, MAR_MGR_CONTEXT_REQUIRED: '1' },
|
|
174
174
|
...(typeof payload.personalInstructions === 'string'
|
|
175
175
|
? { personalInstructions: payload.personalInstructions }
|
|
176
176
|
: {}),
|
|
@@ -142,6 +142,27 @@ export class CodexManagedRunController {
|
|
|
142
142
|
this.host.emit(runId, 'run.running', { threadId, turnId }, 'RUNNING');
|
|
143
143
|
return;
|
|
144
144
|
}
|
|
145
|
+
if (notification.method === 'thread/goal/updated' &&
|
|
146
|
+
activeRun.goalMcp === true &&
|
|
147
|
+
turnId !== undefined &&
|
|
148
|
+
threadId !== undefined &&
|
|
149
|
+
isPlainRecord(params.goal) &&
|
|
150
|
+
params.goal.threadId === threadId &&
|
|
151
|
+
Number.isSafeInteger(params.goal.createdAt) &&
|
|
152
|
+
params.goal.createdAt > 0 &&
|
|
153
|
+
typeof params.goal.objective === 'string' &&
|
|
154
|
+
params.goal.objective.length <= 20_000 &&
|
|
155
|
+
(params.goal.status !== 'active' ||
|
|
156
|
+
(params.goal.tokensUsed === 0 && params.goal.timeUsedSeconds === 0)) &&
|
|
157
|
+
(params.goal.status === 'active' || params.goal.status === 'complete')) {
|
|
158
|
+
this.host.nativeGoalUpdated(runId, {
|
|
159
|
+
threadId,
|
|
160
|
+
createdAt: params.goal.createdAt,
|
|
161
|
+
objective: params.goal.objective,
|
|
162
|
+
status: params.goal.status === 'complete' ? 'completed' : 'active'
|
|
163
|
+
});
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
145
166
|
if (notification.requestId !== undefined &&
|
|
146
167
|
(notification.method === 'item/commandExecution/requestApproval' ||
|
|
147
168
|
notification.method === 'item/fileChange/requestApproval')) {
|
|
@@ -364,7 +385,7 @@ export class CodexManagedRunController {
|
|
|
364
385
|
return;
|
|
365
386
|
}
|
|
366
387
|
try {
|
|
367
|
-
this.runner.prepareEnvironment(secretEnvironment, runId);
|
|
388
|
+
this.runner.prepareEnvironment({ ...secretEnvironment, MAR_MGR_CONTEXT_REQUIRED: '1' }, runId);
|
|
368
389
|
}
|
|
369
390
|
catch (error) {
|
|
370
391
|
this.host.emit(runId, 'run.rejected', { code: safeErrorCode(error, 'SECRET_ENVIRONMENT_INVALID') }, 'FAILED');
|
|
@@ -686,7 +686,7 @@ export function allowsUntrustedWebFetchTls(environment) {
|
|
|
686
686
|
return environment['MAR_AGENT_WEB_FETCH_ALLOW_UNTRUSTED_TLS'] !== '0';
|
|
687
687
|
}
|
|
688
688
|
async function resolveMarAgentHostRuntimeConfiguration(secretEnvironment = {}) {
|
|
689
|
-
const environment = { ...process.env, ...secretEnvironment };
|
|
689
|
+
const environment = { ...process.env, ...secretEnvironment, MAR_MGR_CONTEXT_REQUIRED: '1' };
|
|
690
690
|
const ripgrep = await availableBundledRipgrep();
|
|
691
691
|
return {
|
|
692
692
|
environment: ripgrep === undefined ? environment : prependBundledRipgrepToPath(environment, ripgrep),
|
|
@@ -108,7 +108,10 @@ export class OpenCodeManagedRunController {
|
|
|
108
108
|
let mcp;
|
|
109
109
|
let attachments;
|
|
110
110
|
try {
|
|
111
|
-
environment =
|
|
111
|
+
environment = {
|
|
112
|
+
...parseSecretEnvironment(payload.secretEnvironment),
|
|
113
|
+
MAR_MGR_CONTEXT_REQUIRED: '1'
|
|
114
|
+
};
|
|
112
115
|
mcpSecretEnvironment = parseSecretEnvironment(payload.mcpSecretEnvironment);
|
|
113
116
|
mcp = this.runner.mcpConfiguration(payload.mcpInstallations, {
|
|
114
117
|
...environment,
|
|
@@ -102,18 +102,19 @@ export class GoalRuntime {
|
|
|
102
102
|
throw new Error('GOAL_OBJECTIVE_INVALID');
|
|
103
103
|
const current = this.goals.get(sessionId);
|
|
104
104
|
const continuing = current?.status === 'active';
|
|
105
|
+
const now = this.now();
|
|
105
106
|
const goal = {
|
|
106
107
|
id: continuing ? current.id : randomUUID(),
|
|
107
108
|
sessionId,
|
|
108
109
|
objective: normalized,
|
|
109
|
-
startedAt: continuing ? current.startedAt :
|
|
110
|
+
startedAt: continuing ? current.startedAt : now,
|
|
111
|
+
revisionStartedAt: now,
|
|
110
112
|
revision: continuing ? current.revision + 1 : 1,
|
|
111
113
|
status: 'active',
|
|
112
114
|
turns: 0,
|
|
113
115
|
retryRequired: false,
|
|
114
116
|
retryStartedAtTurn: null,
|
|
115
|
-
consecutiveNoProgressTurns: 0
|
|
116
|
-
continuationPaused: false
|
|
117
|
+
consecutiveNoProgressTurns: 0
|
|
117
118
|
};
|
|
118
119
|
this.goals.set(sessionId, goal);
|
|
119
120
|
this.executionFailures.delete(sessionId);
|
|
@@ -144,34 +145,32 @@ export class GoalRuntime {
|
|
|
144
145
|
if (!input.failed && !input.progressed) {
|
|
145
146
|
this.executionFailures.delete(sessionId);
|
|
146
147
|
const consecutiveNoProgressTurns = current.consecutiveNoProgressTurns + 1;
|
|
147
|
-
const
|
|
148
|
+
const completed = consecutiveNoProgressTurns >= 3;
|
|
148
149
|
const goal = {
|
|
149
150
|
...current,
|
|
150
151
|
consecutiveNoProgressTurns,
|
|
151
|
-
|
|
152
|
+
...(completed ? { status: 'completed' } : {})
|
|
152
153
|
};
|
|
153
154
|
this.goals.set(sessionId, goal);
|
|
154
155
|
this.changed(goal);
|
|
155
|
-
return
|
|
156
|
+
return completed ? 'completed' : 'continue';
|
|
156
157
|
}
|
|
157
158
|
if (!input.failed) {
|
|
158
159
|
this.executionFailures.delete(sessionId);
|
|
159
|
-
if (current.consecutiveNoProgressTurns > 0
|
|
160
|
+
if (current.consecutiveNoProgressTurns > 0) {
|
|
160
161
|
const goal = {
|
|
161
162
|
...current,
|
|
162
|
-
consecutiveNoProgressTurns: 0
|
|
163
|
-
continuationPaused: false
|
|
163
|
+
consecutiveNoProgressTurns: 0
|
|
164
164
|
};
|
|
165
165
|
this.goals.set(sessionId, goal);
|
|
166
166
|
this.changed(goal);
|
|
167
167
|
}
|
|
168
168
|
return 'continue';
|
|
169
169
|
}
|
|
170
|
-
if (current.consecutiveNoProgressTurns > 0
|
|
170
|
+
if (current.consecutiveNoProgressTurns > 0) {
|
|
171
171
|
const goal = {
|
|
172
172
|
...current,
|
|
173
|
-
consecutiveNoProgressTurns: 0
|
|
174
|
-
continuationPaused: false
|
|
173
|
+
consecutiveNoProgressTurns: 0
|
|
175
174
|
};
|
|
176
175
|
this.goals.set(sessionId, goal);
|
|
177
176
|
}
|
|
@@ -191,12 +190,11 @@ export class GoalRuntime {
|
|
|
191
190
|
const current = this.goals.get(sessionId);
|
|
192
191
|
if (current?.status !== 'active')
|
|
193
192
|
return current;
|
|
194
|
-
if (
|
|
193
|
+
if (current.consecutiveNoProgressTurns === 0)
|
|
195
194
|
return current;
|
|
196
195
|
const goal = {
|
|
197
196
|
...current,
|
|
198
|
-
consecutiveNoProgressTurns: 0
|
|
199
|
-
continuationPaused: false
|
|
197
|
+
consecutiveNoProgressTurns: 0
|
|
200
198
|
};
|
|
201
199
|
this.goals.set(sessionId, goal);
|
|
202
200
|
this.changed(goal);
|
|
@@ -247,7 +245,7 @@ export class GoalRuntime {
|
|
|
247
245
|
const heading = kind === 'start' ? 'A Goal has started or changed.' : 'Continue the active Goal.';
|
|
248
246
|
const open = kind === 'start' ? START_OPEN : CONTINUE_OPEN;
|
|
249
247
|
const close = kind === 'start' ? START_CLOSE : CONTINUE_CLOSE;
|
|
250
|
-
return `${open}\n${heading}\n\nOriginal objective:\n${goal.objective}\n\nContinue executing this Goal across turns:\n- Work on the objective now. If it is incomplete, use tools to finish a coherent unit or obtain decisive evidence; do not stop at an acknowledgement, status, plan, or promise.\n- Preserve the original scope. Before completion, review the full objective and verify it with real evidence, then call mar_update_goal with status=completed.\n- Adapt and retry after individual failures. Request blocked only after the same blocker persists for three turns and the required additional retry.\n- Use mar_get_goal only when you need to reread the authoritative objective or status.\n- After mar_update_goal confirms completed or finally blocked, summarize the completed work, verification evidence, remaining issues, and the tool-reported elapsedWallMs converted from milliseconds into a concise readable duration in your final user-facing response. Name whether the Goal completed or is finally blocked.
|
|
248
|
+
return `${open}\n${heading}\n\nOriginal objective:\n${goal.objective}\n\nContinue executing this Goal across turns:\n- This Goal is owned by the Node's myagentroam_goal MCP server. Prefer myagentroam_goal.mar_update_goal to complete it. In Codex, a runner-native Goal created during this Goal may also complete it when Codex reports that native Goal complete; native Goal state from another task or turn does not count. Do not claim completion based only on a final message or an unrelated Goal.\n- Work on the objective now. If it is incomplete, use tools to finish a coherent unit or obtain decisive evidence; do not stop at an acknowledgement, status, plan, or promise.\n- Preserve the original scope. Before completion, review the full objective and verify it with real evidence, then call mar_update_goal with status=completed when available.\n- Adapt and retry after individual failures. Request blocked only after the same blocker persists for three turns and the required additional retry.\n- Use mar_get_goal only when you need to reread the authoritative objective or status.\n- After mar_update_goal confirms completed or finally blocked, summarize the completed work, verification evidence, remaining issues, and the tool-reported elapsedWallMs converted from milliseconds into a concise readable duration in your final user-facing response. If Codex instead confirms completion with its native Goal, summarize the work without claiming a MAR elapsedWallMs value. Name whether the Goal completed or is finally blocked. MAR elapsedWallMs includes waiting and retries through the status update, not tool execution time or the final response. If blocked returns active for the required retry, continue work instead of claiming a final summary.\n${close}`;
|
|
251
249
|
}
|
|
252
250
|
requireCurrent(sessionId, goalId, revision) {
|
|
253
251
|
const current = this.goals.get(sessionId);
|
|
@@ -38,8 +38,11 @@ export class TerminalService {
|
|
|
38
38
|
if (commandMode &&
|
|
39
39
|
(input.workspaceId !== undefined ||
|
|
40
40
|
typeof input.command !== 'string' ||
|
|
41
|
-
input.command.trim().length === 0
|
|
41
|
+
input.command.trim().length === 0 ||
|
|
42
|
+
(input.binary !== undefined && typeof input.binary !== 'boolean')))
|
|
42
43
|
throw new Error('TERMINAL_COMMAND_INVALID');
|
|
44
|
+
if (!commandMode && input.binary !== undefined)
|
|
45
|
+
throw new Error('TERMINAL_OPEN_INVALID');
|
|
43
46
|
const secretEnvironment = parseSecretEnvironment(input.secretEnvironment);
|
|
44
47
|
if ((commandMode || secretEnvironment.MAR_GIT_TOKEN !== undefined) &&
|
|
45
48
|
(!Number.isSafeInteger(input.__requestUserId) || Number(input.__requestUserId) <= 0))
|
|
@@ -58,11 +61,11 @@ export class TerminalService {
|
|
|
58
61
|
const options = {
|
|
59
62
|
...input,
|
|
60
63
|
workspaceId: typeof input.workspaceId === 'string' ? input.workspaceId : null,
|
|
61
|
-
environment: secretEnvironment,
|
|
64
|
+
environment: { ...secretEnvironment, MAR_MGR_CONTEXT_REQUIRED: '1' },
|
|
62
65
|
...(cwd === undefined ? {} : { cwd })
|
|
63
66
|
};
|
|
64
67
|
const terminal = commandMode
|
|
65
|
-
? this.terminalManager.openCommand(options, input.command)
|
|
68
|
+
? this.terminalManager.openCommand(options, input.command, input.binary === true)
|
|
66
69
|
: this.terminalManager.open(options);
|
|
67
70
|
if (commandMode || secretEnvironment.MAR_GIT_TOKEN !== undefined)
|
|
68
71
|
this.terminalOwners.set(terminal.id, {
|
package/dist/terminal.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { spawn as spawnProcess } from 'node:child_process';
|
|
2
3
|
import { existsSync } from 'node:fs';
|
|
3
4
|
import { homedir } from 'node:os';
|
|
4
5
|
import { join } from 'node:path';
|
|
@@ -26,6 +27,7 @@ export class TerminalManager {
|
|
|
26
27
|
idleTimeoutMs;
|
|
27
28
|
now;
|
|
28
29
|
ptyFactory;
|
|
30
|
+
pipeFactory;
|
|
29
31
|
terminate;
|
|
30
32
|
terminals = new Map();
|
|
31
33
|
attachments = new Map();
|
|
@@ -61,6 +63,7 @@ export class TerminalManager {
|
|
|
61
63
|
}
|
|
62
64
|
this.now = options.now ?? Date.now;
|
|
63
65
|
this.ptyFactory = options.ptyFactory ?? nodePtyFactory;
|
|
66
|
+
this.pipeFactory = options.pipeFactory ?? nodePipeFactory;
|
|
64
67
|
this.terminate =
|
|
65
68
|
options.terminate ?? ((child, platform) => terminateProcessTree(child, platform));
|
|
66
69
|
this.idleTimer = setInterval(() => void this.closeExpired(), Math.min(this.idleTimeoutMs, 60_000));
|
|
@@ -81,13 +84,13 @@ export class TerminalManager {
|
|
|
81
84
|
return this.openInternal(input);
|
|
82
85
|
}
|
|
83
86
|
/** A command starts only after a relay or direct Terminal attachment is active. */
|
|
84
|
-
openCommand(input, command) {
|
|
87
|
+
openCommand(input, command, binary = false) {
|
|
85
88
|
if (typeof command !== 'string' ||
|
|
86
89
|
command.trim().length === 0 ||
|
|
87
90
|
Buffer.byteLength(command, 'utf8') > TERMINAL_COMMAND_MAX_BYTES ||
|
|
88
91
|
command.includes('\0'))
|
|
89
92
|
throw new Error('TERMINAL_COMMAND_INVALID');
|
|
90
|
-
return this.openInternal(input, command);
|
|
93
|
+
return this.openInternal(input, command, binary);
|
|
91
94
|
}
|
|
92
95
|
startCommand(terminalId) {
|
|
93
96
|
const terminal = this.requireTerminal(terminalId);
|
|
@@ -96,17 +99,25 @@ export class TerminalManager {
|
|
|
96
99
|
return;
|
|
97
100
|
delete terminal.deferredCommand;
|
|
98
101
|
try {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
if (terminal.binary) {
|
|
103
|
+
const pipe = this.pipeFactory(deferred.shell, deferred.args, deferred.options);
|
|
104
|
+
terminal.pipe = pipe;
|
|
105
|
+
pipe.onData((data) => this.recordBinaryOutput(terminal, data));
|
|
106
|
+
pipe.onExit((exitCode) => this.exited(terminal, exitCode));
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
const pty = this.ptyFactory(deferred.shell, deferred.args, deferred.options);
|
|
110
|
+
terminal.pty = pty;
|
|
111
|
+
pty.onData((data) => this.recordOutput(terminal, data));
|
|
112
|
+
pty.onExit((event) => this.exited(terminal, event.exitCode));
|
|
113
|
+
}
|
|
103
114
|
}
|
|
104
115
|
catch {
|
|
105
116
|
this.emit({ type: 'error', terminalId, code: 'TERMINAL_COMMAND_START_FAILED' });
|
|
106
117
|
this.exited(terminal, 127);
|
|
107
118
|
}
|
|
108
119
|
}
|
|
109
|
-
openInternal(input = {}, command) {
|
|
120
|
+
openInternal(input = {}, command, binary = false) {
|
|
110
121
|
const profileId = input.profileId ?? this.profiles.find((profile) => profile.isDefault)?.id;
|
|
111
122
|
if (typeof profileId !== 'string')
|
|
112
123
|
throw new Error('TERMINAL_PROFILE_INVALID');
|
|
@@ -143,6 +154,8 @@ export class TerminalManager {
|
|
|
143
154
|
workspaceId: input.workspaceId ?? null,
|
|
144
155
|
profile,
|
|
145
156
|
pty,
|
|
157
|
+
pipe: undefined,
|
|
158
|
+
binary,
|
|
146
159
|
...(commandArgs === undefined
|
|
147
160
|
? {}
|
|
148
161
|
: { deferredCommand: { shell: profile.shell, args: commandArgs, options } }),
|
|
@@ -252,12 +265,23 @@ export class TerminalManager {
|
|
|
252
265
|
terminal.updatedAt = this.now();
|
|
253
266
|
terminal.lastUsedAt = terminal.updatedAt;
|
|
254
267
|
if (parsed.data.type === 'input') {
|
|
268
|
+
if (terminal.binary) {
|
|
269
|
+
if (terminal.pipe === undefined)
|
|
270
|
+
throw new Error('TERMINAL_COMMAND_NOT_STARTED');
|
|
271
|
+
const bytes = Buffer.from(parsed.data.data, 'base64');
|
|
272
|
+
if (bytes.length > 32 * 1024 || bytes.toString('base64') !== parsed.data.data)
|
|
273
|
+
throw new Error('TERMINAL_FRAME_INVALID');
|
|
274
|
+
terminal.pipe.write(bytes);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
255
277
|
if (terminal.pty === undefined)
|
|
256
278
|
throw new Error('TERMINAL_COMMAND_NOT_STARTED');
|
|
257
279
|
terminal.pty.write(parsed.data.data);
|
|
258
280
|
return;
|
|
259
281
|
}
|
|
260
282
|
if (parsed.data.type === 'resize') {
|
|
283
|
+
if (terminal.binary)
|
|
284
|
+
throw new Error('TERMINAL_FRAME_INVALID');
|
|
261
285
|
if (terminal.pty === undefined)
|
|
262
286
|
throw new Error('TERMINAL_COMMAND_NOT_STARTED');
|
|
263
287
|
terminal.pty.resize(parsed.data.cols, parsed.data.rows);
|
|
@@ -279,6 +303,8 @@ export class TerminalManager {
|
|
|
279
303
|
try {
|
|
280
304
|
if (terminal.pty !== undefined)
|
|
281
305
|
await this.terminate(terminal.pty, this.platform);
|
|
306
|
+
if (terminal.pipe !== undefined)
|
|
307
|
+
await this.terminate(terminal.pipe, this.platform);
|
|
282
308
|
}
|
|
283
309
|
finally {
|
|
284
310
|
this.emit({ type: 'exit', terminalId: terminal.id, exitCode: terminal.exitCode });
|
|
@@ -331,6 +357,31 @@ export class TerminalManager {
|
|
|
331
357
|
});
|
|
332
358
|
}
|
|
333
359
|
}
|
|
360
|
+
recordBinaryOutput(terminal, data) {
|
|
361
|
+
if (terminal.state !== 'RUNNING')
|
|
362
|
+
return;
|
|
363
|
+
for (let offset = 0; offset < data.length; offset += 32 * 1024) {
|
|
364
|
+
const part = data.subarray(offset, offset + 32 * 1024);
|
|
365
|
+
const frame = {
|
|
366
|
+
sequence: ++terminal.sequence,
|
|
367
|
+
data: part.toString('base64'),
|
|
368
|
+
bytes: part.length
|
|
369
|
+
};
|
|
370
|
+
terminal.replay.push(frame);
|
|
371
|
+
terminal.replayBytes += frame.bytes;
|
|
372
|
+
while (terminal.replayBytes > this.maxReplayBytes && terminal.replay.length > 0) {
|
|
373
|
+
const removed = terminal.replay.shift();
|
|
374
|
+
if (removed !== undefined)
|
|
375
|
+
terminal.replayBytes -= removed.bytes;
|
|
376
|
+
}
|
|
377
|
+
this.emit({
|
|
378
|
+
type: 'output',
|
|
379
|
+
terminalId: terminal.id,
|
|
380
|
+
sequence: frame.sequence,
|
|
381
|
+
data: frame.data
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
334
385
|
exited(terminal, exitCode) {
|
|
335
386
|
if (terminal.state !== 'RUNNING')
|
|
336
387
|
return;
|
|
@@ -433,6 +484,33 @@ function nodePtyFactory(shell, args, options) {
|
|
|
433
484
|
});
|
|
434
485
|
return pty;
|
|
435
486
|
}
|
|
487
|
+
function nodePipeFactory(shell, args, options) {
|
|
488
|
+
const child = spawnProcess(shell, [...args], {
|
|
489
|
+
cwd: options.cwd,
|
|
490
|
+
env: options.env,
|
|
491
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
492
|
+
detached: process.platform !== 'win32',
|
|
493
|
+
windowsHide: true
|
|
494
|
+
});
|
|
495
|
+
child.stdin.on('error', () => undefined);
|
|
496
|
+
child.stderr.resume();
|
|
497
|
+
return {
|
|
498
|
+
pid: child.pid,
|
|
499
|
+
kill: (signal) => child.kill(signal),
|
|
500
|
+
write: (bytes) => {
|
|
501
|
+
if (child.stdin.destroyed)
|
|
502
|
+
throw new Error('TERMINAL_PIPE_CLOSED');
|
|
503
|
+
child.stdin.write(bytes);
|
|
504
|
+
},
|
|
505
|
+
onData: (listener) => {
|
|
506
|
+
child.stdout.on('data', listener);
|
|
507
|
+
},
|
|
508
|
+
onExit: (listener) => {
|
|
509
|
+
child.on('error', () => listener(127));
|
|
510
|
+
child.on('close', (exitCode) => listener(exitCode ?? 1));
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
}
|
|
436
514
|
export function defaultTerminalProfiles(platform, findWindowsExecutable = resolveWindowsExecutable) {
|
|
437
515
|
if (platform === 'win32') {
|
|
438
516
|
const pwsh = findWindowsExecutable('pwsh.exe');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.132",
|
|
4
4
|
"description": "MyAgentRoam Node runtime CLI.",
|
|
5
5
|
"license": "Apache-2.0 AND GPL-2.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -12,7 +12,11 @@
|
|
|
12
12
|
"NOTICE"
|
|
13
13
|
],
|
|
14
14
|
"bin": {
|
|
15
|
-
"mar-node": "./dist/main.js"
|
|
15
|
+
"mar-node": "./dist/main.js",
|
|
16
|
+
"marmgr": "./dist/marmgr.js",
|
|
17
|
+
"marnodes": "./dist/marnodes.js",
|
|
18
|
+
"marssh": "./dist/marssh.js",
|
|
19
|
+
"marscp": "./dist/marscp.js"
|
|
16
20
|
},
|
|
17
21
|
"engines": {
|
|
18
22
|
"node": ">=22.13"
|
|
@@ -30,9 +34,10 @@
|
|
|
30
34
|
"npm": "11.12.1",
|
|
31
35
|
"ws": "^8.21.3",
|
|
32
36
|
"zod": "4.4.3",
|
|
33
|
-
"@myagentroam/
|
|
34
|
-
"@myagentroam/
|
|
35
|
-
"@myagentroam/
|
|
37
|
+
"@myagentroam/marmgr": "0.9.115",
|
|
38
|
+
"@myagentroam/agent": "0.9.132",
|
|
39
|
+
"@myagentroam/protocol": "0.9.132",
|
|
40
|
+
"@myagentroam/code-navigation": "0.9.129"
|
|
36
41
|
},
|
|
37
42
|
"devDependencies": {
|
|
38
43
|
"@types/ws": "^8.18.1"
|