@myagentroam/node 0.9.0 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/connector.d.ts +2 -0
  2. package/dist/connector.js +50 -25
  3. package/dist/control-outbound-scheduler.d.ts +4 -1
  4. package/dist/control-outbound-scheduler.js +39 -6
  5. package/dist/database.d.ts +0 -19
  6. package/dist/database.js +1 -62
  7. package/dist/event-buffer.d.ts +2 -5
  8. package/dist/event-buffer.js +3 -12
  9. package/dist/native-session-history.js +105 -26
  10. package/dist/runner/abstract-runner.d.ts +1 -1
  11. package/dist/runner/abstract-runner.js +2 -2
  12. package/dist/runner/claude/managed-run-controller.d.ts +0 -1
  13. package/dist/runner/claude/managed-run-controller.js +32 -17
  14. package/dist/runner/claude-code-runner.js +3 -4
  15. package/dist/runner/codex/conversation-parser.d.ts +3 -3
  16. package/dist/runner/codex/conversation-parser.js +33 -12
  17. package/dist/runner/codex/managed-run-controller.d.ts +2 -2
  18. package/dist/runner/codex/managed-run-controller.js +26 -10
  19. package/dist/runner/codex-runner.d.ts +3 -1
  20. package/dist/runner/codex-runner.js +40 -10
  21. package/dist/runner/opencode/conversation-parser.d.ts +11 -7
  22. package/dist/runner/opencode/conversation-parser.js +16 -16
  23. package/dist/runner/opencode/managed-run-controller.d.ts +8 -2
  24. package/dist/runner/opencode/managed-run-controller.js +94 -22
  25. package/dist/runner/opencode-runner.d.ts +3 -0
  26. package/dist/runner/opencode-runner.js +25 -0
  27. package/dist/runner-command-engine.js +1 -1
  28. package/dist/runner-profiles.js +29 -17
  29. package/dist/runner-usage.js +5 -5
  30. package/dist/runtime-state.d.ts +5 -12
  31. package/dist/runtime-state.js +32 -46
  32. package/dist/service/attachment-domain-state.d.ts +2 -0
  33. package/dist/service/attachment-domain-state.js +2 -0
  34. package/dist/service/conversation-history-service.js +55 -24
  35. package/dist/service/conversation-segment-service.d.ts +20 -0
  36. package/dist/service/conversation-segment-service.js +156 -0
  37. package/dist/service/external-session-resume-service.js +9 -0
  38. package/dist/service/fake-managed-run-service.d.ts +0 -1
  39. package/dist/service/fake-managed-run-service.js +0 -2
  40. package/dist/service/mcp-installation-verifier.js +1 -1
  41. package/dist/service/native-session-projection-service.js +5 -0
  42. package/dist/service/native-session-watch-service.d.ts +1 -0
  43. package/dist/service/native-session-watch-service.js +21 -4
  44. package/dist/service/node-connection-lifecycle-service.d.ts +0 -2
  45. package/dist/service/node-connection-lifecycle-service.js +0 -1
  46. package/dist/service/node-control-channel.js +1 -1
  47. package/dist/service/node-control-message-service.d.ts +0 -1
  48. package/dist/service/node-control-message-service.js +9 -6
  49. package/dist/service/node-request-service.js +2 -1
  50. package/dist/service/node-workspace-coordinator.d.ts +1 -4
  51. package/dist/service/node-workspace-coordinator.js +2 -12
  52. package/dist/service/run-attachment-service.d.ts +3 -2
  53. package/dist/service/run-attachment-service.js +51 -17
  54. package/dist/service/run-event-service.d.ts +2 -0
  55. package/dist/service/run-event-service.js +77 -2
  56. package/dist/service/run-workbench-service.d.ts +1 -1
  57. package/dist/service/run-workbench-service.js +1 -5
  58. package/dist/service/runner-interaction-service.d.ts +1 -2
  59. package/dist/service/runner-interaction-service.js +1 -4
  60. package/dist/service/session-catalog-service.js +4 -4
  61. package/dist/service/session-context-service.d.ts +1 -0
  62. package/dist/service/session-context-service.js +14 -0
  63. package/dist/service/session-domain-state.d.ts +1 -0
  64. package/dist/service/session-domain-state.js +1 -0
  65. package/dist/service/session-message-service.js +5 -10
  66. package/dist/service/session-presentation-service.d.ts +1 -0
  67. package/dist/service/session-presentation-service.js +8 -1
  68. package/dist/service/session-query-service.d.ts +4 -0
  69. package/dist/service/session-query-service.js +6 -1
  70. package/dist/service/skill-directory-service.d.ts +3 -1
  71. package/dist/service/skill-directory-service.js +31 -30
  72. package/dist/service/skill-install-service.js +4 -74
  73. package/dist/service/skill-node-operation-service.d.ts +1 -1
  74. package/dist/service/skill-node-operation-service.js +6 -3
  75. package/dist/service/workbench-event-service.d.ts +1 -13
  76. package/dist/service/workbench-event-service.js +2 -36
  77. package/dist/service/workbench-manifest-service.d.ts +0 -1
  78. package/dist/service/workbench-manifest-service.js +0 -1
  79. package/dist/service/workspace-domain-state.d.ts +1 -0
  80. package/dist/service/workspace-git-exclude-service.d.ts +4 -0
  81. package/dist/service/workspace-git-exclude-service.js +119 -0
  82. package/dist/service/workspace-queue-workbench-service.d.ts +1 -0
  83. package/dist/service/workspace-queue-workbench-service.js +4 -2
  84. package/dist/service/workspace-service.js +2 -0
  85. package/dist/service/workspace-upload-service.d.ts +2 -0
  86. package/dist/service/workspace-upload-service.js +28 -6
  87. package/dist/service/workspace-workbench-service.d.ts +0 -3
  88. package/dist/service/workspace-workbench-service.js +0 -17
  89. package/dist/supervisor.js +2 -1
  90. package/dist/terminal.js +1 -1
  91. package/dist/util/node-operation-parsers.js +2 -2
  92. package/dist/util/runner-error.d.ts +6 -0
  93. package/dist/util/runner-error.js +52 -0
  94. package/dist/util/runner-native-session-parsers.d.ts +2 -3
  95. package/dist/util/runner-native-session-parsers.js +48 -20
  96. package/dist/util/safe-error.d.ts +2 -0
  97. package/dist/util/safe-error.js +5 -0
  98. package/package.json +2 -2
@@ -8,7 +8,7 @@ const CLAUDE_PLAN_TAG_CLOSE = '</plan_mode>';
8
8
  */
9
9
  export function withClaudePlanTag(input) {
10
10
  return `${CLAUDE_PLAN_TAG_OPEN}
11
- 当前处于 Plan Mode。只做只读分析与规划,不得创建、修改或删除文件,不得执行有副作用的命令。完成分析后,把最终计划完整包裹在 <proposed_plan> </proposed_plan> 标签中输出,不要在标签外重复计划正文。
11
+ You are in Plan Mode. Perform only read-only analysis and planning. Do not create, modify, or delete files, and do not run commands with side effects. Wrap the complete final plan in <proposed_plan> and </proposed_plan> tags without repeating the plan outside those tags.
12
12
  ${CLAUDE_PLAN_TAG_CLOSE}
13
13
 
14
14
  ${input}`;
@@ -1,3 +1,7 @@
1
+ const COMMON_COMMAND_DESCRIPTIONS = {
2
+ compact: 'Compact the current session context.',
3
+ plan: 'Toggle collaborative Plan Mode.'
4
+ };
1
5
  const CLAUDE_MODEL_ENVIRONMENT = {
2
6
  opus: 'ANTHROPIC_DEFAULT_OPUS_MODEL',
3
7
  sonnet: 'ANTHROPIC_DEFAULT_SONNET_MODEL',
@@ -14,24 +18,28 @@ const definitions = {
14
18
  runner: 'codex',
15
19
  models: [],
16
20
  accessOptions: [
17
- { id: 'on-request', label: 'On-request', description: '由 Codex 在需要额外权限时请求确认。' },
21
+ {
22
+ id: 'on-request',
23
+ label: 'On-request',
24
+ description: 'Ask when additional permission is required.'
25
+ },
18
26
  {
19
27
  id: 'workspace-write',
20
28
  label: 'Workspace write',
21
- description: '允许 Codex 在当前工作区内写入文件。'
29
+ description: 'Allow writes in the current Workspace.'
22
30
  },
23
31
  {
24
32
  id: 'full-access',
25
33
  label: 'Full access',
26
- description: '允许 Codex 以完整权限运行,且不再逐项确认。'
34
+ description: 'Allow full access without individual confirmations.'
27
35
  },
28
- { id: 'read-only', label: 'Read-only', description: '限制 Codex 为只读沙箱。' }
36
+ { id: 'read-only', label: 'Read-only', description: 'Restrict Codex to a read-only sandbox.' }
29
37
  ],
30
38
  commands: [
31
39
  {
32
40
  id: 'compact',
33
41
  label: '/compact',
34
- description: '立即压缩当前会话上下文。',
42
+ description: COMMON_COMMAND_DESCRIPTIONS.compact,
35
43
  inputHint: '/compact',
36
44
  interaction: 'IMMEDIATE_ACTION'
37
45
  },
@@ -39,14 +47,14 @@ const definitions = {
39
47
  id: 'plan',
40
48
  label: '/plan',
41
49
  stateLabel: 'Plan Mode',
42
- description: '开启或关闭协作规划模式。',
50
+ description: COMMON_COMMAND_DESCRIPTIONS.plan,
43
51
  inputHint: '/plan',
44
52
  interaction: 'TOGGLE'
45
53
  },
46
54
  {
47
55
  id: 'fast',
48
56
  label: '/fast',
49
- description: '开启或关闭当前模型可用的快速服务层。',
57
+ description: 'Toggle the fast service tier when supported.',
50
58
  inputHint: '/fast',
51
59
  interaction: 'TOGGLE'
52
60
  }
@@ -74,21 +82,25 @@ const definitions = {
74
82
  { id: 'haiku', label: 'Claude Haiku', supportedEfforts: ['low', 'medium'] }
75
83
  ],
76
84
  accessOptions: [
77
- { id: 'default', label: 'Default', description: ' Claude Code 默认规则确认危险操作。' },
85
+ { id: 'default', label: 'Default', description: 'Use Claude Code default confirmations.' },
78
86
  {
79
87
  id: 'accept-edits',
80
88
  label: 'Accept edits',
81
- description: '自动接受当前工作区内的文件编辑。'
89
+ description: 'Automatically accept edits in the current Workspace.'
90
+ },
91
+ {
92
+ id: 'bypass',
93
+ label: 'Bypass permissions',
94
+ description: 'Bypass permission confirmations.'
82
95
  },
83
- { id: 'bypass', label: 'Bypass permissions', description: '跳过 Claude Code 的权限确认。' },
84
- { id: 'plan', label: 'Plan Mode', description: '以 Claude Code 的计划模式运行,不执行工具。' }
96
+ { id: 'plan', label: 'Plan Mode', description: 'Run in Plan Mode without executing tools.' }
85
97
  ],
86
98
  commands: [
87
99
  {
88
100
  id: 'compact',
89
101
  runner: 'claude-code',
90
102
  label: '/compact',
91
- description: '由 Claude Agent SDK 在当前会话中压缩上下文。',
103
+ description: COMMON_COMMAND_DESCRIPTIONS.compact,
92
104
  inputHint: '/compact',
93
105
  source: 'CLAUDE_AGENT_SDK',
94
106
  interaction: 'IMMEDIATE_ACTION',
@@ -100,7 +112,7 @@ const definitions = {
100
112
  runner: 'claude-code',
101
113
  label: '/plan',
102
114
  stateLabel: 'Plan Mode',
103
- description: '开启或关闭标签式规划模式,只读规划并输出计划卡。',
115
+ description: COMMON_COMMAND_DESCRIPTIONS.plan,
104
116
  inputHint: '/plan',
105
117
  source: 'CLAUDE_AGENT_SDK',
106
118
  interaction: 'TOGGLE',
@@ -116,12 +128,12 @@ const definitions = {
116
128
  {
117
129
  id: 'default',
118
130
  label: 'Default',
119
- description: ' OpenCode 和当前 Workspace 配置确认权限。'
131
+ description: 'Use OpenCode and Workspace permission configuration.'
120
132
  },
121
133
  {
122
134
  id: 'auto',
123
135
  label: 'Auto approve',
124
- description: '自动允许 OpenCode 请求的工具权限。'
136
+ description: 'Automatically allow requested tool permissions.'
125
137
  }
126
138
  ],
127
139
  commands: [
@@ -129,7 +141,7 @@ const definitions = {
129
141
  id: 'compact',
130
142
  runner: 'opencode',
131
143
  label: '/compact',
132
- description: '通过 OpenCode 压缩当前会话上下文。',
144
+ description: COMMON_COMMAND_DESCRIPTIONS.compact,
133
145
  inputHint: '/compact',
134
146
  source: 'OPENCODE_SERVER',
135
147
  interaction: 'IMMEDIATE_ACTION',
@@ -141,7 +153,7 @@ const definitions = {
141
153
  runner: 'opencode',
142
154
  label: '/plan',
143
155
  stateLabel: 'Plan Mode',
144
- description: '开启或关闭 OpenCode 原生 Plan Agent。',
156
+ description: COMMON_COMMAND_DESCRIPTIONS.plan,
145
157
  inputHint: '/plan',
146
158
  source: 'OPENCODE_SERVER',
147
159
  interaction: 'TOGGLE',
@@ -70,9 +70,9 @@ export class RunnerUsageReader {
70
70
  'Content-Type': 'application/json'
71
71
  });
72
72
  const windows = [
73
- usageWindow(value, 'five_hour', '5 小时额度'),
74
- usageWindow(value, 'seven_day', '7 天额度'),
75
- usageWindow(value, 'seven_day_sonnet', '7 Sonnet 额度')
73
+ usageWindow(value, 'five_hour', '5-hour limit'),
74
+ usageWindow(value, 'seven_day', '7-day limit'),
75
+ usageWindow(value, 'seven_day_sonnet', '7-day Sonnet limit')
76
76
  ].filter((window) => window !== undefined);
77
77
  if (windows.length === 0)
78
78
  throw new Error('USAGE_RESPONSE_INVALID');
@@ -158,9 +158,9 @@ function codexUsageWindow(value, key) {
158
158
  const window = record(record(value)?.rate_limit)?.[key];
159
159
  const duration = number(record(window)?.limit_window_seconds);
160
160
  const label = duration !== undefined && approximately(duration, 5 * 60 * 60)
161
- ? '5 小时额度'
161
+ ? '5-hour limit'
162
162
  : duration !== undefined && approximately(duration, 7 * 24 * 60 * 60)
163
- ? '7 天额度'
163
+ ? '7-day limit'
164
164
  : undefined;
165
165
  return label === undefined ? undefined : usageWindow(value, key, label);
166
166
  }
@@ -1,5 +1,5 @@
1
1
  import type { AgentRunStatus, RunnerName } from '@myagentroam/protocol';
2
- import type { ConversationItemPage, ConversationPage, NativeSessionControl, NodeAgentRun, NodeAgentSession, NodeApproval, NodeConversationTurn, NodeMessageAttachmentInput, NodeRunEvent, NodeWorkspace, WorkspaceActivityPage } from './database.js';
2
+ import type { ConversationItemPage, ConversationPage, NativeSessionControl, NodeAgentRun, NodeAgentSession, NodeApproval, NodeConversationTurn, NodeMessageAttachmentInput, NodeRunEvent, NodeWorkspace } from './database.js';
3
3
  /**
4
4
  * Deliberately process-local runtime domain. No method in this class writes
5
5
  * into Node SQLite: a Node restart loses active runs, approvals, leases and
@@ -10,22 +10,22 @@ export declare class NodeRuntimeState {
10
10
  private readonly nodeId;
11
11
  private readonly getWorkspace;
12
12
  private readonly now;
13
+ private static readonly MAX_COMPLETED_TURNS_PER_SESSION;
13
14
  private readonly sessions;
14
15
  private readonly runs;
15
16
  private readonly messageRuns;
16
17
  private readonly events;
17
18
  private readonly turns;
18
19
  private readonly approvals;
19
- private readonly workspaceEvents;
20
20
  /** A Session owns at most one running turn; Workspace sessions may run concurrently. */
21
21
  private readonly leases;
22
22
  /** Session FIFO metadata is deliberately process-local with the rest of run state. */
23
23
  private readonly queuedRuns;
24
24
  private readonly queueVersions;
25
25
  private readonly pausedQueues;
26
- private readonly snapshots;
26
+ /** Bounded tombstones reject stale internal start envelopes after terminal cleanup. */
27
+ private readonly terminalRunIds;
27
28
  private nextEventId;
28
- private nextWorkspaceEventId;
29
29
  constructor(nodeId: () => string, getWorkspace: (id: string) => NodeWorkspace | undefined, now?: () => number);
30
30
  activeWorkspaceLeaseCount(): number;
31
31
  activeSessionRun(sessionId: string): NodeAgentRun | undefined;
@@ -138,11 +138,6 @@ export declare class NodeRuntimeState {
138
138
  readonly outputPreview: string;
139
139
  readonly failed?: boolean;
140
140
  }): NodeConversationTurn;
141
- listWorkspaceActivity(input: {
142
- readonly workspaceId: string;
143
- readonly cursor?: string;
144
- readonly limit?: number;
145
- }): WorkspaceActivityPage;
146
141
  createApproval(input: {
147
142
  readonly runId: string;
148
143
  readonly approvalId: string;
@@ -156,8 +151,7 @@ export declare class NodeRuntimeState {
156
151
  expireDueApprovals(): readonly NodeApproval[];
157
152
  listNonterminalRuns(): readonly NodeAgentRun[];
158
153
  lastRunSequence(id: string): number;
159
- saveSnapshot(id: string, value: unknown): void;
160
- getSnapshot(id: string): unknown | undefined;
154
+ removeTerminalRun(runId: string): void;
161
155
  hasActiveWorkspaceLease(workspaceId: string): boolean;
162
156
  /** Removes a terminal Session's process-local projection after its native history is deleted. */
163
157
  removeAgentSession(sessionId: string): void;
@@ -168,7 +162,6 @@ export declare class NodeRuntimeState {
168
162
  private requireSession;
169
163
  private requireRun;
170
164
  private requireApproval;
171
- private recordWorkspaceActivity;
172
165
  private updateTurnForRun;
173
166
  private projectEvent;
174
167
  }
@@ -9,22 +9,22 @@ export class NodeRuntimeState {
9
9
  nodeId;
10
10
  getWorkspace;
11
11
  now;
12
+ static MAX_COMPLETED_TURNS_PER_SESSION = 10;
12
13
  sessions = new Map();
13
14
  runs = new Map();
14
15
  messageRuns = new Map();
15
16
  events = new Map();
16
17
  turns = new Map();
17
18
  approvals = new Map();
18
- workspaceEvents = new Map();
19
19
  /** A Session owns at most one running turn; Workspace sessions may run concurrently. */
20
20
  leases = new Map();
21
21
  /** Session FIFO metadata is deliberately process-local with the rest of run state. */
22
22
  queuedRuns = new Map();
23
23
  queueVersions = new Map();
24
24
  pausedQueues = new Set();
25
- snapshots = new Map();
25
+ /** Bounded tombstones reject stale internal start envelopes after terminal cleanup. */
26
+ terminalRunIds = new Set();
26
27
  nextEventId = 1;
27
- nextWorkspaceEventId = 1;
28
28
  constructor(nodeId, getWorkspace, now = Date.now) {
29
29
  this.nodeId = nodeId;
30
30
  this.getWorkspace = getWorkspace;
@@ -73,6 +73,8 @@ export class NodeRuntimeState {
73
73
  }
74
74
  /** Internal envelope compatibility for a Run already accepted in this process. */
75
75
  adoptRun(input) {
76
+ if (this.terminalRunIds.has(input.runId))
77
+ throw new Error('RUN_TERMINAL');
76
78
  let session = this.sessions.get(input.sessionId);
77
79
  if (session === undefined) {
78
80
  const now = this.now();
@@ -316,11 +318,6 @@ export class NodeRuntimeState {
316
318
  this.queuedRuns.set(session.id, [...(this.queuedRuns.get(session.id) ?? []), run.id]);
317
319
  this.bumpQueueVersion(session.id);
318
320
  this.turns.set(session.id, [...(this.turns.get(session.id) ?? []), fixedTurn]);
319
- this.recordWorkspaceActivity(run.workspaceId, 'run.created', {
320
- runId: run.id,
321
- sessionId: session.id,
322
- runner: run.runner
323
- });
324
321
  this.replaceSession(session.id, { lastActivityAt: now });
325
322
  return { run, created: true };
326
323
  }
@@ -402,7 +399,6 @@ export class NodeRuntimeState {
402
399
  this.bumpQueueVersion(run.sessionId);
403
400
  this.runs.delete(runId);
404
401
  this.events.delete(runId);
405
- this.snapshots.delete(runId);
406
402
  this.turns.set(run.sessionId, (this.turns.get(run.sessionId) ?? []).filter((turn) => turn.runId !== runId));
407
403
  for (const [key, candidate] of this.messageRuns)
408
404
  if (candidate === runId)
@@ -468,12 +464,6 @@ export class NodeRuntimeState {
468
464
  if (input.status !== undefined)
469
465
  this.transitionRun(input.runId, input.status);
470
466
  this.projectEvent(value);
471
- const run = this.requireRun(input.runId);
472
- this.recordWorkspaceActivity(run.workspaceId, 'run.updated', {
473
- runId: run.id,
474
- eventType: input.eventType,
475
- status: input.status ?? null
476
- });
477
467
  return value;
478
468
  }
479
469
  listRunEvents(id, after = 0) {
@@ -559,16 +549,6 @@ export class NodeRuntimeState {
559
549
  this.replaceSession(session.id, { lastActivityAt: now });
560
550
  return completedTurn;
561
551
  }
562
- listWorkspaceActivity(input) {
563
- const all = this.workspaceEvents.get(input.workspaceId) ?? [];
564
- const start = decodeCursor(input.cursor, input.workspaceId);
565
- const limit = Math.min(Math.max(input.limit ?? 30, 1), 500);
566
- const events = all.slice(start, start + limit);
567
- return {
568
- events,
569
- nextCursor: start + limit < all.length ? encodeCursor(input.workspaceId, start + limit) : null
570
- };
571
- }
572
552
  createApproval(input) {
573
553
  this.requireRun(input.runId);
574
554
  if (this.approvals.has(input.approvalId))
@@ -631,11 +611,30 @@ export class NodeRuntimeState {
631
611
  lastRunSequence(id) {
632
612
  return this.events.get(id)?.at(-1)?.sequence ?? 0;
633
613
  }
634
- saveSnapshot(id, value) {
635
- this.snapshots.set(id, value);
636
- }
637
- getSnapshot(id) {
638
- return this.snapshots.get(id);
614
+ removeTerminalRun(runId) {
615
+ const run = this.requireRun(runId);
616
+ if (!terminal(run.status))
617
+ throw new Error('RUN_ACTIVE');
618
+ this.terminalRunIds.add(runId);
619
+ if (this.terminalRunIds.size > 1_024)
620
+ this.terminalRunIds.delete(this.terminalRunIds.values().next().value);
621
+ const queue = this.queuedRuns.get(run.sessionId) ?? [];
622
+ if (queue.includes(runId)) {
623
+ this.queuedRuns.set(run.sessionId, queue.filter((candidate) => candidate !== runId));
624
+ this.bumpQueueVersion(run.sessionId);
625
+ }
626
+ this.runs.delete(runId);
627
+ this.events.delete(runId);
628
+ const turns = this.turns.get(run.sessionId) ?? [];
629
+ const completed = turns.filter((turn) => terminalTurn(turn.status));
630
+ const retainedCompleted = new Set(completed.slice(-NodeRuntimeState.MAX_COMPLETED_TURNS_PER_SESSION).map((turn) => turn.id));
631
+ this.turns.set(run.sessionId, turns.filter((turn) => !terminalTurn(turn.status) || retainedCompleted.has(turn.id)));
632
+ for (const [key, candidate] of this.messageRuns)
633
+ if (candidate === runId)
634
+ this.messageRuns.delete(key);
635
+ for (const [approvalId, approval] of this.approvals)
636
+ if (approval.runId === runId)
637
+ this.approvals.delete(approvalId);
639
638
  }
640
639
  hasActiveWorkspaceLease(workspaceId) {
641
640
  return [...this.leases.values()]
@@ -658,7 +657,6 @@ export class NodeRuntimeState {
658
657
  for (const runId of runIds) {
659
658
  this.runs.delete(runId);
660
659
  this.events.delete(runId);
661
- this.snapshots.delete(runId);
662
660
  }
663
661
  for (const [key, runId] of this.messageRuns)
664
662
  if (runIds.has(runId))
@@ -681,7 +679,6 @@ export class NodeRuntimeState {
681
679
  for (const runId of runIds) {
682
680
  this.runs.delete(runId);
683
681
  this.events.delete(runId);
684
- this.snapshots.delete(runId);
685
682
  }
686
683
  for (const [key, runId] of this.messageRuns)
687
684
  if (runIds.has(runId))
@@ -689,7 +686,6 @@ export class NodeRuntimeState {
689
686
  for (const [approvalId, approval] of this.approvals)
690
687
  if (runIds.has(approval.runId))
691
688
  this.approvals.delete(approvalId);
692
- this.workspaceEvents.delete(workspaceId);
693
689
  for (const sessionId of sessionIds) {
694
690
  this.queuedRuns.delete(sessionId);
695
691
  this.queueVersions.delete(sessionId);
@@ -730,19 +726,6 @@ export class NodeRuntimeState {
730
726
  throw new Error('APPROVAL_NOT_FOUND');
731
727
  return value;
732
728
  }
733
- recordWorkspaceActivity(workspaceId, eventType, payload) {
734
- const event = {
735
- id: this.nextWorkspaceEventId++,
736
- workspaceId,
737
- eventType,
738
- payload,
739
- createdAt: this.now()
740
- };
741
- this.workspaceEvents.set(workspaceId, [
742
- ...(this.workspaceEvents.get(workspaceId) ?? []),
743
- event
744
- ]);
745
- }
746
729
  updateTurnForRun(runId, update) {
747
730
  const run = this.requireRun(runId);
748
731
  const current = this.turns.get(run.sessionId) ?? [];
@@ -840,6 +823,9 @@ export class NodeRuntimeState {
840
823
  function terminal(status) {
841
824
  return ['SUCCEEDED', 'FAILED', 'CANCELLED', 'INTERRUPTED', 'LOST'].includes(status);
842
825
  }
826
+ function terminalTurn(status) {
827
+ return ['SUCCEEDED', 'FAILED', 'CANCELLED', 'INTERRUPTED'].includes(status);
828
+ }
843
829
  function transitionAllowed(from, to) {
844
830
  return !terminal(from) || from === to;
845
831
  }
@@ -1,5 +1,7 @@
1
1
  export declare class AttachmentDomainState<TCachedRunImages, TNativeImageSource> {
2
2
  readonly runDirectories: Map<string, string>;
3
+ readonly runFileDirectories: Map<string, string>;
3
4
  readonly runImages: Map<string, TCachedRunImages>;
4
5
  readonly nativeHistoryImages: Map<string, TNativeImageSource[]>;
6
+ readonly nativeHistoryImageTimers: Map<string, NodeJS.Timeout>;
5
7
  }
@@ -1,5 +1,7 @@
1
1
  export class AttachmentDomainState {
2
2
  runDirectories = new Map();
3
+ runFileDirectories = new Map();
3
4
  runImages = new Map();
4
5
  nativeHistoryImages = new Map();
6
+ nativeHistoryImageTimers = new Map();
5
7
  }
@@ -16,35 +16,66 @@ export class ConversationHistoryService {
16
16
  async read(session, input) {
17
17
  const snapshotSequence = this.options.snapshotSequence();
18
18
  const readAt = Date.now();
19
- const official = await this.readOfficial(session, input);
19
+ let official = await this.readOfficial(session, input);
20
20
  if (official !== undefined) {
21
+ const limit = Math.min(Math.max(input.limit ?? 30, 1), 500);
22
+ const runtimeTurns = this.options.runtime.getAgentSession(session.id) === undefined
23
+ ? []
24
+ : this.options.runtime.listConversationTurns({ sessionId: session.id, limit: 500 }).turns;
25
+ const initialOfficialRunIds = new Set(official.turns.flatMap((turn) => (turn.runId === null ? [] : [turn.runId])));
26
+ const absentLocalTurns = runtimeTurns.filter((turn) => turn.runId === null || !initialOfficialRunIds.has(turn.runId));
27
+ const officialFloor = Math.min(...official.turns.map((turn) => Math.max(turn.startedAt ?? 0, turn.completedAt ?? 0)));
28
+ const missingLocalTurns = official.turns.length === 0
29
+ ? absentLocalTurns.slice(-limit)
30
+ : officialFloor > 0
31
+ ? absentLocalTurns.filter((turn) => Math.max(turn.startedAt ?? 0, turn.completedAt ?? 0) >= officialFloor)
32
+ : absentLocalTurns.slice(-1);
33
+ if (input.cursor === undefined &&
34
+ missingLocalTurns.length > 0 &&
35
+ official.turns.length + missingLocalTurns.length > limit) {
36
+ const reserved = Math.min(missingLocalTurns.length, Math.max(0, limit - 1));
37
+ const narrowed = await this.readOfficial(session, { ...input, limit: limit - reserved });
38
+ if (narrowed !== undefined)
39
+ official = narrowed;
40
+ }
21
41
  const officialTurns = await this.attachNativeImages(session, official.turns);
22
42
  const officialRunIds = new Set(officialTurns.flatMap((turn) => (turn.runId === null ? [] : [turn.runId])));
23
- const localTurns = this.options.runtime.getAgentSession(session.id) === undefined
24
- ? []
25
- : this.options.runtime
26
- .listConversationTurns({ sessionId: session.id, limit: 500 })
27
- .turns.flatMap((turn) => {
28
- if (turn.runId === null || !officialRunIds.has(turn.runId))
29
- return [turn];
30
- const pendingItems = turn.items.filter((item) => item.kind === 'user_input_request' && item.status === 'PENDING');
31
- return pendingItems.length === 0 ? [] : [{ ...turn, items: pendingItems }];
32
- });
33
- const localRequestIds = new Set(localTurns.flatMap((turn) => turn.items.flatMap((item) => {
34
- const requestId = item.kind === 'user_input_request' && isPlainRecord(item.payload)
35
- ? item.payload.requestId
36
- : undefined;
37
- return typeof requestId === 'string' ? [requestId] : [];
38
- })));
43
+ const pendingItemsByRunId = new Map();
44
+ for (const turn of runtimeTurns) {
45
+ if (turn.runId === null || !officialRunIds.has(turn.runId))
46
+ continue;
47
+ const pendingItems = turn.items.filter((item) => item.kind === 'user_input_request' && item.status === 'PENDING');
48
+ if (pendingItems.length > 0)
49
+ pendingItemsByRunId.set(turn.runId, pendingItems);
50
+ }
51
+ const availableLocalSlots = Math.max(0, limit - officialTurns.length);
52
+ const localCandidates = (input.cursor === undefined ? missingLocalTurns : []).sort((left, right) => (left.startedAt ?? 0) - (right.startedAt ?? 0));
53
+ const localTurns = availableLocalSlots === 0 ? [] : localCandidates.slice(-availableLocalSlots);
54
+ const localRequestIds = new Set([
55
+ ...localTurns.flatMap((turn) => turn.items.flatMap((item) => {
56
+ const requestId = item.kind === 'user_input_request' && isPlainRecord(item.payload)
57
+ ? item.payload.requestId
58
+ : undefined;
59
+ return typeof requestId === 'string' ? [requestId] : [];
60
+ })),
61
+ ...[...pendingItemsByRunId.values()].flatMap((items) => items.flatMap((item) => {
62
+ const requestId = isPlainRecord(item.payload) ? item.payload.requestId : undefined;
63
+ return typeof requestId === 'string' ? [requestId] : [];
64
+ }))
65
+ ]);
39
66
  const turns = [
40
67
  ...officialTurns
41
- .map((turn) => ({
42
- ...turn,
43
- items: turn.items.filter((item) => item.kind !== 'user_input_request' ||
44
- !isPlainRecord(item.payload) ||
45
- typeof item.payload.requestId !== 'string' ||
46
- !localRequestIds.has(item.payload.requestId))
47
- }))
68
+ .map((turn) => {
69
+ const pendingItems = turn.runId === null ? [] : (pendingItemsByRunId.get(turn.runId) ?? []);
70
+ const items = [
71
+ ...turn.items.filter((item) => item.kind !== 'user_input_request' ||
72
+ !isPlainRecord(item.payload) ||
73
+ typeof item.payload.requestId !== 'string' ||
74
+ !localRequestIds.has(item.payload.requestId)),
75
+ ...pendingItems
76
+ ];
77
+ return { ...turn, items };
78
+ })
48
79
  .filter((turn) => turn.items.length > 0),
49
80
  ...localTurns
50
81
  ].sort((left, right) => (left.startedAt ?? 0) - (right.startedAt ?? 0));
@@ -0,0 +1,20 @@
1
+ import type { ConversationSegment, ConversationSegmentPage } from '@myagentroam/protocol';
2
+ import type { NodeAgentSession, NodeConversationTurn } from '../database.js';
3
+ import type { ConversationHistoryPage } from '../util/runner-native-session-parsers.js';
4
+ export declare function conversationSegments(turn: NodeConversationTurn): ConversationSegment[];
5
+ export declare function conversationSegmentIdentityForItem(turn: NodeConversationTurn, itemId: string, itemKind?: string): {
6
+ readonly id: string;
7
+ readonly index: number;
8
+ };
9
+ export declare class ConversationSegmentService {
10
+ private readonly readTurns;
11
+ constructor(readTurns: (session: NodeAgentSession, input: {
12
+ readonly cursor?: string;
13
+ readonly limit?: number;
14
+ }) => Promise<ConversationHistoryPage>);
15
+ projectInitial(session: NodeAgentSession, page: ConversationHistoryPage, limit: number): ConversationSegmentPage;
16
+ read(session: NodeAgentSession, input: {
17
+ readonly cursor?: string;
18
+ readonly limit?: number;
19
+ }): Promise<ConversationSegmentPage>;
20
+ }