@myagentroam/node 0.11.36 → 0.11.38
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/application/execution-lifecycle.js +54 -0
- package/dist/application/node-lifecycle.js +104 -0
- package/dist/application/node-operation-admission.js +51 -0
- package/dist/application/node-operation-contracts.js +1 -0
- package/dist/application/runner-composition.js +22 -0
- package/dist/application/session-projection-composition.js +26 -0
- package/dist/application/workspace-application.js +61 -0
- package/dist/application/workspace-composition.js +21 -0
- package/dist/capabilities.js +2 -3
- package/dist/codex-app-server.js +1 -471
- package/dist/connection/authorization.js +26 -0
- package/dist/{service → connection}/direct-transfer-service.js +1 -1
- package/dist/connection/endpoints.js +32 -0
- package/dist/connector.js +145 -253
- package/dist/control-outbound-scheduler.js +3 -0
- package/dist/database.js +34 -14
- package/dist/git-askpass.js +1 -1
- package/dist/git-execution-helper.js +1 -1
- package/dist/hosting/hosted-conversation-file-identity.js +27 -0
- package/dist/hosting/hosted-conversation-file-operations.js +44 -0
- package/dist/hosting/hosted-conversation-node-service.js +974 -0
- package/dist/hosting/hosted-conversation-runs.js +599 -0
- package/dist/hosting/hosted-conversation-templates.js +280 -0
- package/dist/hosting/hosted-conversation-uploads.js +344 -0
- package/dist/hosting/hosted-conversation-watch.js +194 -0
- package/dist/hosting/runtime-contracts.js +1 -0
- package/dist/runner/abstract-runner.js +4 -13
- package/dist/{claude-agent-sdk.js → runner/claude/claude-agent-sdk.js} +2 -2
- package/dist/runner/claude/conversation-parser.js +2 -1
- package/dist/runner/claude/managed-run-controller.js +6 -7
- package/dist/runner/claude/message-parsers.js +2 -1
- package/dist/runner/claude/native-transcript.js +2 -1
- package/dist/runner/claude/tool-normalizer.js +1 -1
- package/dist/runner/claude-code-runner.js +2 -2
- package/dist/runner/codex/codex-app-server.js +471 -0
- package/dist/runner/codex/conversation-parser.js +4 -2
- package/dist/runner/codex/managed-run-controller.js +7 -9
- package/dist/runner/codex/native-transcript.js +1 -1
- package/dist/runner/codex/tool-normalizer.js +1 -1
- package/dist/runner/codex-runner.js +9 -27
- package/dist/runner/contracts.js +1 -0
- package/dist/runner/mar-agent/conversation-normalizer.js +2 -2
- package/dist/runner/mar-agent/history-projection.js +1 -1
- package/dist/runner/mar-agent/hosted-conversation-runtime.js +6 -10
- package/dist/runner/mar-agent/managed-run-controller.js +85 -61
- package/dist/runner/mar-agent/runtime-model-factory.js +0 -3
- package/dist/runner/mar-agent-runner.js +1 -1
- package/dist/runner/normalization.js +42 -0
- package/dist/runner/opencode/conversation-parser.js +1 -1
- package/dist/runner/opencode/managed-run-controller.js +6 -7
- package/dist/{opencode-server.js → runner/opencode/opencode-server.js} +2 -2
- package/dist/runner/opencode/tool-normalizer.js +1 -1
- package/dist/runner/opencode-runner.js +4 -5
- package/dist/runner/runner-registry.js +4 -1
- package/dist/runtime-state.js +48 -372
- package/dist/service/computer-use-enablement-service.js +1 -1
- package/dist/service/continuous-run-coordinator.js +52 -7
- package/dist/service/conversation-history-service.js +4 -4
- package/dist/service/conversation-hosting-capability-service.js +1 -1
- package/dist/service/conversation-hosting-enablement-service.js +1 -1
- package/dist/service/conversation-hosting-lifecycle-service.js +3 -3
- package/dist/service/conversation-hosting-maintenance-service.js +1 -1
- package/dist/service/conversation-observation.js +1 -1
- package/dist/service/conversation-segment-service.js +1 -1
- package/dist/service/external-session-resume-service.js +47 -27
- package/dist/service/fake-managed-run-service.js +3 -7
- package/dist/service/git-operation-service.js +1 -1
- package/dist/service/goal-continuation-coordinator.js +14 -7
- package/dist/service/mar-agent-model-transport-test-service.js +1 -1
- package/dist/service/native-session-watch-service.js +1 -1
- package/dist/service/node-connection-lifecycle-service.js +6 -3
- package/dist/service/node-control-channel.js +48 -11
- package/dist/service/node-request-service.js +1 -1
- package/dist/service/node-terminal-channel.js +2 -1
- package/dist/service/node-workspace-coordinator.js +3 -2
- package/dist/service/notification-mcp-bridge.js +6 -2
- package/dist/service/recent-run-projection-service.js +1 -1
- package/dist/service/restart-session-restorer.js +1 -1
- package/dist/service/run-event-service.js +49 -42
- package/dist/service/run-workbench-service.js +3 -3
- package/dist/service/runner-interaction-service.js +6 -5
- package/dist/service/runner-service.js +1 -1
- package/dist/service/session-recent-activity.js +2 -1
- package/dist/service/skill-directory-service.js +1 -1
- package/dist/service/skill-install-service.js +2 -2
- package/dist/service/terminal-service.js +1 -1
- package/dist/service/workbench-event-projection.js +3 -2
- package/dist/service/workbench-event-service.js +1 -1
- package/dist/service/workspace-service.js +3 -2
- package/dist/session/attachments.js +110 -0
- package/dist/session/contracts.js +1 -0
- package/dist/{util → session}/conversation-identity.js +1 -1
- package/dist/session/input-validation.js +15 -0
- package/dist/{service → session}/native-session-projection-service.js +40 -11
- package/dist/session/pagination.js +77 -0
- package/dist/{util → session}/runner-native-session-parsers.js +2 -1
- package/dist/session/runtime/approvals.js +90 -0
- package/dist/session/runtime/conversation-projection.js +310 -0
- package/dist/session/runtime/type-guards.js +19 -0
- package/dist/{service → session}/session-catalog-service.js +5 -4
- package/dist/{service → session}/session-command-service.js +3 -3
- package/dist/{service → session}/session-identity-service.js +1 -1
- package/dist/{service → session}/session-lifecycle-service.js +18 -7
- package/dist/{service → session}/session-message-service.js +6 -5
- package/dist/{service → session}/session-presentation-service.js +1 -1
- package/dist/{service → session}/session-query-service.js +2 -2
- package/dist/{service → session}/session-schedule-service.js +2 -2
- package/dist/util/records.js +3 -0
- package/dist/workspace/contracts.js +1 -0
- package/dist/workspace/file-index.js +534 -0
- package/dist/workspace/files.js +466 -0
- package/dist/workspace/git.js +991 -0
- package/dist/workspace/path-access.js +184 -0
- package/dist/workspace/utf8.js +12 -0
- package/dist/{service → workspace}/workspace-change-service.js +45 -21
- package/dist/{service → workspace}/workspace-code-navigation-service.js +6 -6
- package/dist/{service → workspace}/workspace-file-manager-service.js +1 -1
- package/dist/{service → workspace}/workspace-file-service.js +16 -16
- package/dist/{service → workspace}/workspace-git-exclude-service.js +1 -1
- package/dist/{service → workspace}/workspace-queue-workbench-service.js +3 -2
- package/dist/{service → workspace}/workspace-session-service.js +45 -40
- package/dist/{service → workspace}/workspace-upload-service.js +28 -28
- package/dist/{service → workspace}/workspace-watch-service.js +29 -28
- package/dist/{service → workspace}/workspace-workbench-service.js +4 -3
- package/package.json +4 -4
- package/dist/service/hosted-conversation-node-service.js +0 -2252
- package/dist/service/run-domain-state.js +0 -4
- package/dist/service/session-domain-state.js +0 -9
- package/dist/service/workspace-domain-state.js +0 -41
- package/dist/util/node-operation-parsers.js +0 -320
- package/dist/workspace.js +0 -2168
- /package/dist/{connector → application}/node-connector-options.js +0 -0
- /package/dist/{util → connection}/node-operation-data.js +0 -0
- /package/dist/{connector → connection}/node-operation-router.js +0 -0
- /package/dist/{service → hosting}/hosted-conversation-sandbox-profile.js +0 -0
- /package/dist/{service → hosting}/hosted-conversation-store.js +0 -0
- /package/dist/{util → mcp}/secret-environment.js +0 -0
- /package/dist/{claude-channel.js → runner/claude/claude-channel.js} +0 -0
- /package/dist/{util → runner}/personal-instructions.js +0 -0
- /package/dist/{util → runner}/runner-error.js +0 -0
- /package/dist/{service/conversation-work-process.js → session/runtime/work-process.js} +0 -0
- /package/dist/{service → session}/session-context-service.js +0 -0
- /package/dist/{util → workspace}/git-credential-client.js +0 -0
- /package/dist/{service → workspace}/workspace-content-search-service.js +0 -0
- /package/dist/{service → workspace}/workspace-watch-snapshot-service.js +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { denyPermission } from '../claude-agent-sdk.js';
|
|
2
|
-
import { boundedConversationItemId
|
|
1
|
+
import { denyPermission } from '../runner/claude/claude-agent-sdk.js';
|
|
2
|
+
import { boundedConversationItemId } from '../runner/normalization.js';
|
|
3
|
+
import { isTerminalRunStatus } from '../session/runtime/type-guards.js';
|
|
3
4
|
import { claudeUserInputQuestions } from '../runner/claude/conversation-parser.js';
|
|
4
5
|
export class RunnerInteractionService {
|
|
5
6
|
options;
|
|
@@ -223,7 +224,6 @@ export class RunnerInteractionService {
|
|
|
223
224
|
finally {
|
|
224
225
|
codex.execution.runs.delete(runId);
|
|
225
226
|
codex.releaseEnvironment(runId);
|
|
226
|
-
this.options.state.active.delete(runId);
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
229
|
respondUserInput(runId, requestId, rawAnswers) {
|
|
@@ -260,13 +260,14 @@ export class RunnerInteractionService {
|
|
|
260
260
|
if (run === undefined)
|
|
261
261
|
return false;
|
|
262
262
|
if (run.status === 'CANCELLING') {
|
|
263
|
-
const status = this.options.
|
|
263
|
+
const status = this.options.runtime.isExecutionInterruptRequested(runId)
|
|
264
|
+
? 'INTERRUPTED'
|
|
265
|
+
: 'CANCELLED';
|
|
264
266
|
this.options.emitRun(runId, 'run.completed', { status }, status);
|
|
265
267
|
}
|
|
266
268
|
else if (!isTerminalRunStatus(run.status))
|
|
267
269
|
return false;
|
|
268
270
|
this.options.codex().execution.runs.delete(runId);
|
|
269
|
-
this.options.state.active.delete(runId);
|
|
270
271
|
return true;
|
|
271
272
|
}
|
|
272
273
|
respondCodexUserInput(runId, requestId, rawAnswers) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { runnerDefaultConfigurationSchema } from '@myagentroam/protocol';
|
|
2
|
-
import { parseSecretEnvironment } from '../
|
|
2
|
+
import { parseSecretEnvironment } from '../mcp/secret-environment.js';
|
|
3
3
|
export class RunnerService {
|
|
4
4
|
database;
|
|
5
5
|
capabilities;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { isPlainRecord
|
|
1
|
+
import { isPlainRecord } from '../util/records.js';
|
|
2
|
+
import { isTerminalRunStatus } from '../session/runtime/type-guards.js';
|
|
2
3
|
/** Projects only already-observed, process-local public facts. Never reads native history. */
|
|
3
4
|
function activity(runtime, sessionId) {
|
|
4
5
|
const candidate = runtime.activeSessionRun(sessionId);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { lstat, open, readdir, readlink, realpath } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { skillInstallMetadataSchema } from '@myagentroam/protocol';
|
|
4
|
-
import { hasBrokenWorkspaceSkillGitExclude } from '
|
|
4
|
+
import { hasBrokenWorkspaceSkillGitExclude } from '../workspace/workspace-git-exclude-service.js';
|
|
5
5
|
const MAX_METADATA_BYTES = 64 * 1024;
|
|
6
6
|
const SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
7
7
|
const WINDOWS_DEVICE_NAME = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])$/u;
|
|
@@ -4,8 +4,8 @@ import { promisify } from 'node:util';
|
|
|
4
4
|
import { lstat, mkdir, readFile, readdir, readlink, realpath, rename, rm, symlink, writeFile } from 'node:fs/promises';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { skillInstallMetadataSchema } from '@myagentroam/protocol';
|
|
7
|
-
import { GitCommandError, resolveWorkspaceGitRoot } from '../workspace.js';
|
|
8
|
-
import { rewriteWorkspaceGitExclude, updateWorkspaceSkillGitExclude } from '
|
|
7
|
+
import { GitCommandError, resolveWorkspaceGitRoot } from '../workspace/git.js';
|
|
8
|
+
import { rewriteWorkspaceGitExclude, updateWorkspaceSkillGitExclude } from '../workspace/workspace-git-exclude-service.js';
|
|
9
9
|
export class SkillInstallService {
|
|
10
10
|
async recoverNodeHome(home) {
|
|
11
11
|
const agentsRoot = path.join(home, '.agents', 'skills');
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { conversationSegmentIdentityForItem, conversationSegments } from './conversation-segment-service.js';
|
|
2
|
-
import { runConversationItemId } from '../runtime-
|
|
3
|
-
import { isNodeAgentRun, isNodeConversationTurn
|
|
2
|
+
import { runConversationItemId } from '../session/runtime/conversation-projection.js';
|
|
3
|
+
import { isNodeAgentRun, isNodeConversationTurn } from '../session/runtime/type-guards.js';
|
|
4
|
+
import { isPlainRecord } from '../util/records.js';
|
|
4
5
|
export function workbenchPayloadSessionId(getRun, category, payload) {
|
|
5
6
|
if (!isPlainRecord(payload))
|
|
6
7
|
return undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createEnvelope } from '@myagentroam/protocol';
|
|
2
|
-
import { isPlainRecord } from '../util/
|
|
2
|
+
import { isPlainRecord } from '../util/records.js';
|
|
3
3
|
import { projectWorkbenchPayload, workbenchPayloadSessionId } from './workbench-event-projection.js';
|
|
4
4
|
export class WorkbenchEventService {
|
|
5
5
|
options;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { realpath, rmdir } from 'node:fs/promises';
|
|
2
|
-
import { createWorkspaceDirectory,
|
|
3
|
-
import {
|
|
2
|
+
import { createWorkspaceDirectory, listWorkspaceDirectories } from '../workspace/files.js';
|
|
3
|
+
import { inspectWorkspace } from '../workspace/git.js';
|
|
4
|
+
import { ensureWorkspaceGitExclude } from '../workspace/workspace-git-exclude-service.js';
|
|
4
5
|
import { workspaceGitSettingsSchema } from '@myagentroam/protocol';
|
|
5
6
|
export class WorkspaceService {
|
|
6
7
|
database;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { serializeComposerTag, parseComposerTags } from '@myagentroam/protocol';
|
|
2
|
+
import { isPlainRecord } from '../util/records.js';
|
|
3
|
+
const MAX_COMPOSER_ATTACHMENT_BYTES = 5 * 1024 * 1024;
|
|
4
|
+
const MAX_COMPOSER_ATTACHMENTS_BYTES = 10 * 1024 * 1024;
|
|
5
|
+
const COMPOSER_IMAGE_MIME_TYPES = new Set([
|
|
6
|
+
'image/png',
|
|
7
|
+
'image/jpeg',
|
|
8
|
+
'image/gif',
|
|
9
|
+
'image/webp'
|
|
10
|
+
]);
|
|
11
|
+
export function parseComposerAttachmentUpload(input) {
|
|
12
|
+
if (input.composerAttachmentId === undefined && input.mime === undefined)
|
|
13
|
+
return undefined;
|
|
14
|
+
if (typeof input.composerAttachmentId !== 'string' ||
|
|
15
|
+
!isUuid(input.composerAttachmentId) ||
|
|
16
|
+
typeof input.name !== 'string' ||
|
|
17
|
+
!validComposerAttachmentName(input.name) ||
|
|
18
|
+
typeof input.mime !== 'string' ||
|
|
19
|
+
input.mime.length === 0 ||
|
|
20
|
+
input.mime.length > 120)
|
|
21
|
+
throw new Error('COMPOSER_ATTACHMENT_INVALID');
|
|
22
|
+
return { id: input.composerAttachmentId, name: input.name, mime: input.mime };
|
|
23
|
+
}
|
|
24
|
+
export function parseComposerAttachmentIds(value) {
|
|
25
|
+
if (value === undefined)
|
|
26
|
+
return [];
|
|
27
|
+
if (!Array.isArray(value) || value.length > 5 || value.some((id) => typeof id !== 'string'))
|
|
28
|
+
throw new Error('MESSAGE_ATTACHMENTS_INVALID');
|
|
29
|
+
const ids = value;
|
|
30
|
+
if (ids.some((id) => !isUuid(id)) || new Set(ids).size !== ids.length)
|
|
31
|
+
throw new Error('MESSAGE_ATTACHMENTS_INVALID');
|
|
32
|
+
return ids;
|
|
33
|
+
}
|
|
34
|
+
export function validComposerAttachmentName(name) {
|
|
35
|
+
return (name.trim().length > 0 &&
|
|
36
|
+
name.length <= 255 &&
|
|
37
|
+
name !== '.' &&
|
|
38
|
+
name !== '..' &&
|
|
39
|
+
!name.includes('/') &&
|
|
40
|
+
!name.includes('\\') &&
|
|
41
|
+
!Array.from(name).some((character) => {
|
|
42
|
+
const code = character.charCodeAt(0);
|
|
43
|
+
return code <= 31 || code === 127 || '<>:"|?*'.includes(character);
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
export function isUuid(value) {
|
|
47
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
|
|
48
|
+
}
|
|
49
|
+
export function parseMessageAttachments(value) {
|
|
50
|
+
if (value === undefined)
|
|
51
|
+
return [];
|
|
52
|
+
if (!Array.isArray(value) || value.length > 5)
|
|
53
|
+
throw new Error('MESSAGE_ATTACHMENTS_INVALID');
|
|
54
|
+
let totalBytes = 0;
|
|
55
|
+
return value.map((attachment) => {
|
|
56
|
+
if (!isPlainRecord(attachment))
|
|
57
|
+
throw new Error('MESSAGE_ATTACHMENTS_INVALID');
|
|
58
|
+
if (typeof attachment.name !== 'string' ||
|
|
59
|
+
typeof attachment.mime !== 'string' ||
|
|
60
|
+
typeof attachment.dataBase64 !== 'string')
|
|
61
|
+
throw new Error('MESSAGE_ATTACHMENTS_INVALID');
|
|
62
|
+
if (!validComposerAttachmentName(attachment.name) ||
|
|
63
|
+
attachment.mime.length === 0 ||
|
|
64
|
+
attachment.mime.length > 120 ||
|
|
65
|
+
!isCanonicalBase64(attachment.dataBase64))
|
|
66
|
+
throw new Error('MESSAGE_ATTACHMENTS_INVALID');
|
|
67
|
+
const byteSize = Buffer.byteLength(attachment.dataBase64, 'base64');
|
|
68
|
+
if (byteSize === 0 || byteSize > MAX_COMPOSER_ATTACHMENT_BYTES)
|
|
69
|
+
throw new Error('MESSAGE_ATTACHMENTS_INVALID');
|
|
70
|
+
totalBytes += byteSize;
|
|
71
|
+
if (totalBytes > MAX_COMPOSER_ATTACHMENTS_BYTES)
|
|
72
|
+
throw new Error('MESSAGE_ATTACHMENTS_INVALID');
|
|
73
|
+
return { name: attachment.name, mime: attachment.mime, dataBase64: attachment.dataBase64 };
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
export function parseRunnerImageAttachments(value) {
|
|
77
|
+
return parseMessageAttachments(value).map((attachment) => {
|
|
78
|
+
if (!isRunnerImageAttachment(attachment))
|
|
79
|
+
throw new Error('MESSAGE_ATTACHMENT_UNSUPPORTED');
|
|
80
|
+
return attachment;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
export function isRunnerImageAttachment(attachment) {
|
|
84
|
+
return COMPOSER_IMAGE_MIME_TYPES.has(attachment.mime);
|
|
85
|
+
}
|
|
86
|
+
export function isImageAttachment(attachment) {
|
|
87
|
+
return attachment.mime.startsWith('image/');
|
|
88
|
+
}
|
|
89
|
+
export function withNonImageAttachmentPrompt(input, paths, files) {
|
|
90
|
+
if (paths.length === 0)
|
|
91
|
+
return input;
|
|
92
|
+
const tag = serializeComposerTag('mar_attachments', {
|
|
93
|
+
files: files
|
|
94
|
+
? [...files]
|
|
95
|
+
: paths.map((path) => ({
|
|
96
|
+
name: path.split(/[\\/]/u).at(-1),
|
|
97
|
+
mime: 'application/octet-stream',
|
|
98
|
+
path
|
|
99
|
+
}))
|
|
100
|
+
});
|
|
101
|
+
const body = parseComposerTags(input)
|
|
102
|
+
.filter((part) => part.kind !== 'mar_attachments')
|
|
103
|
+
.map((part) => (part.kind === 'text' ? part.text : part.raw))
|
|
104
|
+
.join('')
|
|
105
|
+
.trimEnd();
|
|
106
|
+
return `${body}\n${tag}`;
|
|
107
|
+
}
|
|
108
|
+
export function isCanonicalBase64(value) {
|
|
109
|
+
return value.length > 0 && value.length % 4 === 0 && /^[A-Za-z0-9+/]+={0,2}$/.test(value);
|
|
110
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { composerVisibleText } from '@myagentroam/protocol';
|
|
2
|
+
export function validSessionOption(value) {
|
|
3
|
+
return (value === undefined ||
|
|
4
|
+
(typeof value === 'string' && value.trim().length > 0 && value.length <= 120));
|
|
5
|
+
}
|
|
6
|
+
export function validSessionEffort(value) {
|
|
7
|
+
return value === undefined || (typeof value === 'string' && value.length <= 120);
|
|
8
|
+
}
|
|
9
|
+
export function validSessionTitle(value) {
|
|
10
|
+
return (value === undefined ||
|
|
11
|
+
(typeof value === 'string' && value.trim().length > 0 && value.length <= 160));
|
|
12
|
+
}
|
|
13
|
+
export function sessionTitleFromFirstMessage(content) {
|
|
14
|
+
return composerVisibleText(content).replace(/\s+/g, ' ').slice(0, 160);
|
|
15
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import { deduplicateDirectSessions } from '
|
|
1
|
+
import { deduplicateDirectSessions } from './runner-native-session-parsers.js';
|
|
2
2
|
export class NativeSessionProjectionService {
|
|
3
3
|
options;
|
|
4
|
+
directNative = new Map();
|
|
5
|
+
cachedNative() {
|
|
6
|
+
return [...this.directNative.values()];
|
|
7
|
+
}
|
|
4
8
|
constructor(options) {
|
|
5
9
|
this.options = options;
|
|
6
10
|
}
|
|
@@ -54,7 +58,7 @@ export class NativeSessionProjectionService {
|
|
|
54
58
|
return sessions;
|
|
55
59
|
}
|
|
56
60
|
async resolve(sessionId, registeredOnly = false) {
|
|
57
|
-
const cached = this.
|
|
61
|
+
const cached = this.directNative.get(sessionId);
|
|
58
62
|
if (cached !== undefined)
|
|
59
63
|
return cached;
|
|
60
64
|
const database = this.options.database();
|
|
@@ -152,20 +156,45 @@ export class NativeSessionProjectionService {
|
|
|
152
156
|
async rename(session, input) {
|
|
153
157
|
if (session.externalSessionId === null)
|
|
154
158
|
return;
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
159
|
+
const runner = this.options.runners.require(session.runner);
|
|
160
|
+
const database = this.requireDatabase();
|
|
161
|
+
await runner.renameNative(session, input);
|
|
162
|
+
const runnerTitle = runner.usesRunnerTitleForRename();
|
|
163
|
+
database.saveNativeSessionMetadata({
|
|
164
|
+
runner: session.runner,
|
|
165
|
+
externalSessionId: session.externalSessionId,
|
|
166
|
+
workspaceId: session.workspaceId,
|
|
167
|
+
...(input.title === undefined
|
|
168
|
+
? {}
|
|
169
|
+
: runnerTitle
|
|
170
|
+
? {
|
|
171
|
+
runnerTitle: input.title,
|
|
172
|
+
runnerTitlePending: input.title !== null && input.title.trim().length > 0
|
|
173
|
+
}
|
|
174
|
+
: { customTitle: input.title }),
|
|
175
|
+
...(input.pinned === undefined ? {} : { pinned: input.pinned })
|
|
176
|
+
});
|
|
158
177
|
}
|
|
159
178
|
project(input) {
|
|
160
179
|
const timestamp = input.lastActivityAt ?? 0;
|
|
161
180
|
const database = this.options.database();
|
|
162
181
|
const metadata = database?.getNativeSessionMetadata(input.runner, input.externalSessionId, input.workspace.id);
|
|
163
|
-
const
|
|
182
|
+
const projection = this.options.runners.require(input.runner).projectedRunnerTitle(metadata, {
|
|
164
183
|
externalSessionId: input.externalSessionId,
|
|
165
184
|
workspaceId: input.workspace.id,
|
|
166
185
|
...(input.title === undefined ? {} : { title: input.title }),
|
|
167
186
|
...(input.titleOrigin === undefined ? {} : { titleOrigin: input.titleOrigin })
|
|
168
|
-
}
|
|
187
|
+
});
|
|
188
|
+
if (projection.clearPending) {
|
|
189
|
+
database?.saveNativeSessionMetadata({
|
|
190
|
+
runner: input.runner,
|
|
191
|
+
externalSessionId: input.externalSessionId,
|
|
192
|
+
workspaceId: input.workspace.id,
|
|
193
|
+
runnerTitle: projection.title,
|
|
194
|
+
runnerTitlePending: false
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
const runnerTitle = projection.title;
|
|
169
198
|
return {
|
|
170
199
|
id: input.externalSessionId,
|
|
171
200
|
nodeId: this.options.nodeId(),
|
|
@@ -199,7 +228,7 @@ export class NativeSessionProjectionService {
|
|
|
199
228
|
remember(sessions) {
|
|
200
229
|
const database = this.options.database();
|
|
201
230
|
for (const session of sessions) {
|
|
202
|
-
this.
|
|
231
|
+
this.directNative.set(session.id, session);
|
|
203
232
|
if (database === undefined || session.externalSessionId === null)
|
|
204
233
|
continue;
|
|
205
234
|
const metadata = database.getNativeSessionMetadata(session.runner, session.externalSessionId, session.workspaceId);
|
|
@@ -224,7 +253,7 @@ export class NativeSessionProjectionService {
|
|
|
224
253
|
record.metadata.customTitle ?? record.metadata.runnerTitle ?? 'Untitled session'
|
|
225
254
|
]));
|
|
226
255
|
for (const session of [
|
|
227
|
-
...this.
|
|
256
|
+
...this.directNative.values(),
|
|
228
257
|
...this.options.runtime.listAgentSessions()
|
|
229
258
|
]) {
|
|
230
259
|
if (session.workspaceId !== workspaceId)
|
|
@@ -312,13 +341,13 @@ export class NativeSessionProjectionService {
|
|
|
312
341
|
}
|
|
313
342
|
}
|
|
314
343
|
const directIds = new Set();
|
|
315
|
-
for (const [directId, direct] of this.
|
|
344
|
+
for (const [directId, direct] of this.directNative) {
|
|
316
345
|
if (direct.runner === session.runner &&
|
|
317
346
|
direct.cwd === session.cwd &&
|
|
318
347
|
direct.externalSessionId !== null &&
|
|
319
348
|
nativeIds.has(direct.externalSessionId)) {
|
|
320
349
|
directIds.add(directId);
|
|
321
|
-
this.
|
|
350
|
+
this.directNative.delete(directId);
|
|
322
351
|
}
|
|
323
352
|
}
|
|
324
353
|
for (const projectionId of projectionIds) {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export const DEFAULT_SESSION_PAGE_SIZE = 10;
|
|
2
|
+
export const MAX_SESSION_PAGE_SIZE = 100;
|
|
3
|
+
export function sessionPage(sessions, input) {
|
|
4
|
+
const limit = typeof input.limit === 'number' &&
|
|
5
|
+
Number.isInteger(input.limit) &&
|
|
6
|
+
input.limit >= 1 &&
|
|
7
|
+
input.limit <= MAX_SESSION_PAGE_SIZE
|
|
8
|
+
? input.limit
|
|
9
|
+
: DEFAULT_SESSION_PAGE_SIZE;
|
|
10
|
+
const cursor = typeof input.cursor === 'string' ? input.cursor : undefined;
|
|
11
|
+
const ordered = [...sessions].sort(compareSessionPageEntries);
|
|
12
|
+
const cursorEntry = cursor === undefined ? undefined : parseSessionPageCursor(cursor);
|
|
13
|
+
const afterCursor = cursor === undefined
|
|
14
|
+
? ordered
|
|
15
|
+
: cursorEntry === undefined
|
|
16
|
+
? []
|
|
17
|
+
: ordered.filter((session) => compareSessionPageEntries(session, cursorEntry) > 0);
|
|
18
|
+
const pinned = ordered.filter((session) => session.pinOrder !== null && session.pinOrder !== undefined);
|
|
19
|
+
const pinnedIndexes = new Map(pinned.map((session, index) => [session.id, index]));
|
|
20
|
+
const page = afterCursor.slice(0, limit).map((session) => {
|
|
21
|
+
const pinIndex = pinnedIndexes.get(session.id);
|
|
22
|
+
if (pinIndex === undefined)
|
|
23
|
+
return session;
|
|
24
|
+
return {
|
|
25
|
+
...session,
|
|
26
|
+
canMovePinUp: pinIndex > 0,
|
|
27
|
+
canMovePinDown: pinIndex < pinned.length - 1
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
const last = page.at(-1);
|
|
31
|
+
return {
|
|
32
|
+
sessions: page,
|
|
33
|
+
nextCursor: last !== undefined && afterCursor.length > page.length ? createSessionPageCursor(last) : null
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function parseSessionPageCursor(cursor) {
|
|
37
|
+
try {
|
|
38
|
+
const value = JSON.parse(decodeURIComponent(cursor));
|
|
39
|
+
if (!Array.isArray(value) || value.length !== 3)
|
|
40
|
+
return undefined;
|
|
41
|
+
const [pinOrder, lastActivityAt, id] = value;
|
|
42
|
+
if ((pinOrder !== null && (typeof pinOrder !== 'number' || !Number.isFinite(pinOrder))) ||
|
|
43
|
+
(lastActivityAt !== null &&
|
|
44
|
+
(typeof lastActivityAt !== 'number' || !Number.isFinite(lastActivityAt))) ||
|
|
45
|
+
typeof id !== 'string')
|
|
46
|
+
return undefined;
|
|
47
|
+
return {
|
|
48
|
+
id,
|
|
49
|
+
lastActivityAt: lastActivityAt ?? 0,
|
|
50
|
+
pinOrder,
|
|
51
|
+
externalDiscovered: lastActivityAt !== null
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function createSessionPageCursor(session) {
|
|
59
|
+
return encodeURIComponent(JSON.stringify([
|
|
60
|
+
session.pinOrder ?? null,
|
|
61
|
+
session.externalDiscovered === false ? null : session.lastActivityAt,
|
|
62
|
+
session.id
|
|
63
|
+
]));
|
|
64
|
+
}
|
|
65
|
+
export function compareSessionPageEntries(left, right) {
|
|
66
|
+
const leftPinned = left.pinOrder !== null && left.pinOrder !== undefined;
|
|
67
|
+
const rightPinned = right.pinOrder !== null && right.pinOrder !== undefined;
|
|
68
|
+
if (leftPinned !== rightPinned)
|
|
69
|
+
return leftPinned ? -1 : 1;
|
|
70
|
+
if (leftPinned && rightPinned)
|
|
71
|
+
return (left.pinOrder - right.pinOrder || left.id.localeCompare(right.id));
|
|
72
|
+
const leftFound = left.externalDiscovered !== false;
|
|
73
|
+
const rightFound = right.externalDiscovered !== false;
|
|
74
|
+
if (leftFound !== rightFound)
|
|
75
|
+
return leftFound ? -1 : 1;
|
|
76
|
+
return right.lastActivityAt - left.lastActivityAt || left.id.localeCompare(right.id, 'en');
|
|
77
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { nativeTranscriptAdapter } from '../native-transcript-adapters.js';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
-
import { compactRunnerText,
|
|
3
|
+
import { compactRunnerText, nativePageEnd } from '../runner/normalization.js';
|
|
4
|
+
import { isPlainRecord } from '../util/records.js';
|
|
4
5
|
export async function settleWithin(promise, milliseconds) {
|
|
5
6
|
let timer;
|
|
6
7
|
try {
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export class RuntimeApprovals {
|
|
2
|
+
getRun;
|
|
3
|
+
currentExecutionId;
|
|
4
|
+
canonicalRunId;
|
|
5
|
+
now;
|
|
6
|
+
approvals = new Map();
|
|
7
|
+
constructor(getRun, currentExecutionId, canonicalRunId, now) {
|
|
8
|
+
this.getRun = getRun;
|
|
9
|
+
this.currentExecutionId = currentExecutionId;
|
|
10
|
+
this.canonicalRunId = canonicalRunId;
|
|
11
|
+
this.now = now;
|
|
12
|
+
}
|
|
13
|
+
createApproval(input) {
|
|
14
|
+
const run = this.getRun(input.runId);
|
|
15
|
+
if (run === undefined)
|
|
16
|
+
throw new Error('RUN_NOT_FOUND');
|
|
17
|
+
if (input.runId !== run.id && this.currentExecutionId(run.id) !== input.runId)
|
|
18
|
+
throw new Error('RUN_EXECUTION_NOT_CURRENT');
|
|
19
|
+
if (this.approvals.has(input.approvalId))
|
|
20
|
+
throw new Error('APPROVAL_ALREADY_EXISTS');
|
|
21
|
+
if (input.expiresAt <= this.now())
|
|
22
|
+
throw new Error('APPROVAL_EXPIRED');
|
|
23
|
+
const value = {
|
|
24
|
+
id: input.approvalId,
|
|
25
|
+
runId: input.runId,
|
|
26
|
+
status: 'PENDING',
|
|
27
|
+
payload: input.payload,
|
|
28
|
+
expiresAt: input.expiresAt,
|
|
29
|
+
createdAt: this.now(),
|
|
30
|
+
decidedAt: null
|
|
31
|
+
};
|
|
32
|
+
this.approvals.set(value.id, value);
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
getApproval(id) {
|
|
36
|
+
const value = this.approvals.get(id);
|
|
37
|
+
if (value?.status === 'PENDING' && value.expiresAt <= this.now())
|
|
38
|
+
return this.expireApproval(id);
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
listApprovals(runId) {
|
|
42
|
+
const canonicalRunId = this.canonicalRunId(runId);
|
|
43
|
+
return [...this.approvals.values()]
|
|
44
|
+
.filter((value) => this.canonicalRunId(value.runId) === canonicalRunId)
|
|
45
|
+
.map((value) => this.getApproval(value.id));
|
|
46
|
+
}
|
|
47
|
+
expireApproval(id) {
|
|
48
|
+
const value = this.requireApproval(id);
|
|
49
|
+
if (value.status !== 'PENDING')
|
|
50
|
+
return value;
|
|
51
|
+
if (value.expiresAt > this.now())
|
|
52
|
+
throw new Error('APPROVAL_NOT_EXPIRED');
|
|
53
|
+
const next = { ...value, status: 'EXPIRED', decidedAt: this.now() };
|
|
54
|
+
this.approvals.set(id, next);
|
|
55
|
+
return next;
|
|
56
|
+
}
|
|
57
|
+
decideApproval(id, decision) {
|
|
58
|
+
const value = this.getApproval(id);
|
|
59
|
+
if (value === undefined)
|
|
60
|
+
throw new Error('APPROVAL_NOT_FOUND');
|
|
61
|
+
if (value.status === 'EXPIRED')
|
|
62
|
+
throw new Error('APPROVAL_EXPIRED');
|
|
63
|
+
if (value.status !== 'PENDING')
|
|
64
|
+
throw new Error('APPROVAL_ALREADY_DECIDED');
|
|
65
|
+
const next = { ...value, status: decision, decidedAt: this.now() };
|
|
66
|
+
this.approvals.set(id, next);
|
|
67
|
+
return next;
|
|
68
|
+
}
|
|
69
|
+
expireDueApprovals() {
|
|
70
|
+
return [...this.approvals.values()]
|
|
71
|
+
.filter((value) => value.status === 'PENDING' && value.expiresAt <= this.now())
|
|
72
|
+
.map((value) => this.expireApproval(value.id));
|
|
73
|
+
}
|
|
74
|
+
requireApproval(id) {
|
|
75
|
+
const value = this.approvals.get(id);
|
|
76
|
+
if (value === undefined)
|
|
77
|
+
throw new Error('APPROVAL_NOT_FOUND');
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
removeRuns(runIds, executionIds) {
|
|
81
|
+
for (const [id, approval] of this.approvals)
|
|
82
|
+
if (runIds.has(approval.runId) || executionIds.has(approval.runId))
|
|
83
|
+
this.approvals.delete(id);
|
|
84
|
+
}
|
|
85
|
+
removeRun(runId, executionIds) {
|
|
86
|
+
for (const [id, approval] of this.approvals)
|
|
87
|
+
if (runId === approval.runId || executionIds.has(approval.runId))
|
|
88
|
+
this.approvals.delete(id);
|
|
89
|
+
}
|
|
90
|
+
}
|