nolo-cli 0.1.41 → 0.1.43

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 (136) hide show
  1. package/README.md +27 -12
  2. package/agent-runtime/agentThread.ts +222 -0
  3. package/agent-runtime/agentThreadAdmission.ts +74 -0
  4. package/agent-runtime/dialogWritePlan.ts +42 -0
  5. package/agent-runtime/hostAdapter.ts +1 -0
  6. package/agent-runtime/index.ts +43 -0
  7. package/agent-runtime/localLoop.ts +31 -2
  8. package/agent-runtime/localToolPolicy.ts +1 -0
  9. package/agent-runtime/localWorkspaceTools.ts +1258 -78
  10. package/agent-runtime/noloWorkspaceTools.ts +20 -1
  11. package/agent-runtime/runtimeToolPolicy.ts +1 -5
  12. package/agent-runtime/runtimeToolSurface.ts +1 -0
  13. package/agentAliases.ts +29 -9
  14. package/agentInternalCommandEntries.ts +11 -3
  15. package/agentMachineCommands.ts +1 -0
  16. package/agentRecordCommands.ts +2 -2
  17. package/agentRecordHelpers.ts +59 -6
  18. package/agentRunCommand.ts +84 -19
  19. package/ai/agent/buildSystemPrompt.ts +14 -0
  20. package/ai/agent/cliExecutor.ts +202 -43
  21. package/ai/agent/createAgentSchema.ts +2 -2
  22. package/ai/agent/liveAudioModel.ts +1 -1
  23. package/ai/agent/streamAgentChatTurn.ts +154 -20
  24. package/ai/agent/streamAgentChatTurnUtils.ts +63 -0
  25. package/ai/ai.locale.ts +70 -0
  26. package/ai/chat/inlineImageUrlsForCustomProvider.ts +7 -2
  27. package/ai/chat/sendOpenAICompletionsRequest.ts +25 -1
  28. package/ai/llm/kimi.ts +0 -1
  29. package/ai/llm/openrouterModels.ts +27 -2
  30. package/ai/memory/policy.ts +49 -0
  31. package/ai/memory/queryShared.ts +10 -4
  32. package/ai/memory/recentRelationshipRecap.ts +4 -0
  33. package/ai/memory/remember.ts +48 -104
  34. package/ai/memory/scope.ts +74 -0
  35. package/ai/memory/types.ts +6 -0
  36. package/ai/memory/understanding.ts +10 -18
  37. package/ai/policy/runtimePolicy.ts +95 -23
  38. package/ai/tools/agent/agentTools.ts +0 -21
  39. package/ai/tools/agent/startAgentDialogTool.ts +2 -1
  40. package/ai/tools/index.ts +2 -20
  41. package/ai/tools/prepareTools.ts +171 -1
  42. package/ai/tools/readXhsProfileTool.ts +80 -69
  43. package/ai/tools/table/createTableTool.ts +11 -0
  44. package/ai/tools/toolPacks.ts +19 -0
  45. package/ai/types.ts +3 -0
  46. package/app/favorite/deletedFavoriteProjection.ts +31 -0
  47. package/app/hooks/deleteDbKey.ts +24 -3
  48. package/app/i18n/translations/interface.locale.ts +48 -16
  49. package/app/types/appSummary.ts +4 -0
  50. package/app/utils/desktopAgentRuntimeTurnClient.ts +2 -0
  51. package/auth/adminPermissions.ts +64 -4
  52. package/auth/routes.ts +5 -0
  53. package/chat/chat.locale.ts +4 -0
  54. package/chat/dialog/actions/handleSendMessageAction.ts +7 -2
  55. package/chat/dialog/dialogAttachmentCleanup.ts +135 -0
  56. package/chat/dialog/dialogSlice.ts +117 -4
  57. package/chat/messages/activityCompletion.ts +97 -0
  58. package/chat/messages/imagePayloadPersistence.ts +22 -0
  59. package/chat/messages/messageContent.ts +20 -16
  60. package/chat/messages/messageSlice.ts +20 -1
  61. package/chat/messages/toolThunks.ts +32 -11
  62. package/chat/messages/types.ts +8 -1
  63. package/chat/messages/web/toolDisplayName.ts +686 -0
  64. package/cli/agentAliases.ts +29 -9
  65. package/cli/agentInternalCommandEntries.ts +11 -3
  66. package/cli/agentMachineCommands.ts +1 -0
  67. package/cli/agentRecordCommands.ts +2 -2
  68. package/cli/agentRecordHelpers.ts +59 -6
  69. package/cli/agentRunCommand.ts +84 -19
  70. package/cli/client/agentRun.ts +65 -253
  71. package/cli/client/localRuntimeAdapter.ts +179 -53
  72. package/cli/commandRegistry.ts +7 -4
  73. package/cli/dialogAttachmentCleanup.ts +151 -0
  74. package/cli/dialogCommands.ts +163 -61
  75. package/cli/dialogInternalCommandEntries.ts +1 -1
  76. package/cli/docCreateCommands.ts +345 -0
  77. package/cli/docInternalCommandEntries.ts +11 -0
  78. package/cli/docPageHelpers.ts +80 -0
  79. package/cli/docSkillHelpers.ts +67 -0
  80. package/cli/docSpaceHelpers.ts +46 -0
  81. package/cli/internalCommandEntries.ts +4 -0
  82. package/cli/machineCommands.ts +18 -34
  83. package/cli/memoryCommands.ts +253 -0
  84. package/cli/memoryInternalCommandEntries.ts +9 -0
  85. package/cli/qoderUsageProbe.ts +252 -0
  86. package/cli/scriptCommandEntries.ts +0 -3
  87. package/cli/secretScan.ts +31 -0
  88. package/cli/tableCommands.ts +248 -13
  89. package/cli/workflowInternalCommandEntries.ts +2 -3
  90. package/client/agentRun.test.ts +68 -84
  91. package/client/agentRun.ts +65 -253
  92. package/client/localRuntimeAdapter.test.ts +505 -74
  93. package/client/localRuntimeAdapter.ts +179 -53
  94. package/commandRegistry.ts +7 -4
  95. package/connector-experimental/capabilities.ts +10 -9
  96. package/create/space/sidebarVisibleTypes.ts +19 -0
  97. package/dialogAttachmentCleanup.ts +151 -0
  98. package/dialogCommands.ts +163 -61
  99. package/dialogInternalCommandEntries.ts +1 -1
  100. package/docCreateCommands.ts +345 -0
  101. package/docInternalCommandEntries.ts +11 -0
  102. package/docPageHelpers.ts +80 -0
  103. package/docSkillHelpers.ts +67 -0
  104. package/docSpaceHelpers.ts +46 -0
  105. package/integrations/google/models.ts +4 -4
  106. package/integrations/openai/generateOpenAIRequestBody.ts +0 -3
  107. package/integrations/xhs-reader/types.ts +36 -3
  108. package/integrations/xhs-reader/url.ts +24 -1
  109. package/internalCommandEntries.ts +4 -0
  110. package/machineCommands.ts +18 -34
  111. package/memoryCommands.ts +253 -0
  112. package/memoryInternalCommandEntries.ts +9 -0
  113. package/package.json +141 -114
  114. package/qoderUsageProbe.ts +252 -0
  115. package/render/contentIcon/types.ts +167 -0
  116. package/render/page/docSlice.ts +30 -3
  117. package/render/page/types.ts +2 -0
  118. package/render/table/createTableAction.ts +4 -0
  119. package/render/table/tableSlice.ts +61 -0
  120. package/render/table/types.ts +10 -0
  121. package/scriptCommandEntries.ts +0 -3
  122. package/secretScan.ts +31 -0
  123. package/server/handlers/agentRun/types.ts +24 -23
  124. package/tableCommands.ts +248 -13
  125. package/workflowInternalCommandEntries.ts +2 -3
  126. package/ai/tools/agent/taskRunTool.ts +0 -112
  127. package/cli/client/taskRunPrompt.ts +0 -27
  128. package/cli/taskRunCommand.ts +0 -265
  129. package/client/taskRunPrompt.ts +0 -27
  130. package/integrations/xhs-reader/analyze/profileAnalyzer.ts +0 -205
  131. package/integrations/xhs-reader/backends/playwrightProfileCollector.ts +0 -624
  132. package/integrations/xhs-reader/bridge/readXhsProfileWithBridge.ts +0 -227
  133. package/integrations/xhs-reader/client/xhsWebApiClient.ts +0 -229
  134. package/integrations/xhs-reader/normalize.ts +0 -390
  135. package/integrations/xhs-reader/orchestrator.ts +0 -178
  136. package/taskRunCommand.ts +0 -265
package/README.md CHANGED
@@ -37,6 +37,9 @@ nolo
37
37
  nolo doctor
38
38
  nolo --version
39
39
 
40
+ # no Nolo login required: runs the local Codex CLI in the current directory
41
+ nolo run "review this repository"
42
+
40
43
  # update later from your shell
41
44
  nolo update
42
45
  ```
@@ -48,7 +51,14 @@ Inside the TUI, run:
48
51
  ```
49
52
 
50
53
  The npm package expects Bun to be available because the executable is a Bun
51
- TypeScript script. For agent calls outside this repository, provide a token:
54
+ TypeScript script.
55
+
56
+ For local maintainer workflows, `nolo run "..."` does not require a Nolo
57
+ account. It resolves to the built-in `local-codex` agent, runs in the current
58
+ working directory, and uses the local Codex CLI installation. This is intended
59
+ for repository-local review, triage, and release-check tasks.
60
+
61
+ Remote Nolo records and synced workflows still require a token:
52
62
 
53
63
  ```bash
54
64
  NOLO_SERVER=https://nolo.chat AUTH_TOKEN=<token> nolo
@@ -87,6 +97,8 @@ nolo --help
87
97
  nolo doctor
88
98
  nolo update
89
99
  nolo
100
+ nolo run "review this repository"
101
+ nolo chat "triage the failing tests"
90
102
  nolo chat
91
103
  nolo connect
92
104
  nolo connect --watch
@@ -107,8 +119,8 @@ nolo chat --agent agent-pub-01APPBUILDER00000001YAII3I --msg "你好"
107
119
 
108
120
  For document creation, prefer the CLI over direct scripts. Use `nolo doc create`
109
121
  for ordinary pages and `nolo skill-doc create` for skill-backed pages. Both
110
- support `--sync`, `--dry-run`, `--json`, and `--allow-secrets`; scripts remain
111
- the lower-level fallback for debugging.
122
+ support `--sync`, `--dry-run`, `--json`, and `--allow-secrets`, and both write
123
+ for the current auth token user rather than a demo bootstrap user.
112
124
 
113
125
  Experimental machine connector commands:
114
126
 
@@ -176,7 +188,7 @@ nolo agent list --space <space>
176
188
  nolo dialog list
177
189
  nolo dialog list --space <space>
178
190
  nolo dialog read <dialog>
179
- nolo dialog delete <dialog> --yes
191
+ nolo dialog delete <dialog...> --yes
180
192
  nolo doc list --agent <agent>
181
193
  nolo table query --table meta-0e95801d90-NOLOTASKBOARD --limit 20
182
194
  nolo table query --table meta-0e95801d90-NOLOTASKBOARD --columns '["title","status","owner","priority","codeStatus"]' --no-base-fields --output items
@@ -208,14 +220,17 @@ commands instead of ad hoc scripts:
208
220
  updates such as `--model`, `--cli-provider`, `--api-source`, and repeated
209
221
  `--field key=value`.
210
222
 
211
- Account ownership matters for private agent maintenance. The legacy
212
- `b2e06f801f` / platform-demo account still owns older agents, task-board data,
213
- and workspace records, while newer personal or machine-bound work may belong to
214
- the current operator account. Use `nolo agent read`, `nolo agent update`, and
215
- repo-native helpers as the normal entrypoints; only owner-only operations such
216
- as private record updates/deletes should use the maintained platform-demo token
217
- from `scripts/testUtils.ts` or a regenerated token from `scripts/generateToken.ts`.
218
- Do not copy token strings into docs, logs, or prompts.
223
+ Account ownership matters for private agent maintenance. Default new tests,
224
+ validation dialogs, task-board writes, machine-bound agent runs, and
225
+ user-visible results should write to the current operator account
226
+ `0e95801d90`. The legacy
227
+ `b2e06f801f` / platform-demo account still owns older public/shared records, but
228
+ it is being retired as the default write target. Use `nolo agent read`,
229
+ `nolo agent update`, and repo-native helpers as the normal entrypoints; only
230
+ owner-only operations that explicitly target legacy records should use the
231
+ maintained platform-demo token from `scripts/testUtils.ts` or a regenerated
232
+ token from `scripts/generateToken.ts`. Do not copy token strings into docs,
233
+ logs, or prompts.
219
234
 
220
235
  Inside the current TUI, examples of supported slash commands include:
221
236
  `/agent`, `/agents`, `/switch`, `/context` (alias `/ctx`), `/dialog`, `/doc`,
@@ -0,0 +1,222 @@
1
+ export const AGENT_THREAD_RECORD_PREFIX = "agent-thread";
2
+ export const AGENT_THREAD_INDEX_PREFIX = "agent-threadidx";
3
+
4
+ export const AGENT_THREAD_STATUSES = [
5
+ "pending",
6
+ "running",
7
+ "done",
8
+ "failed",
9
+ "cancelled",
10
+ ] as const;
11
+
12
+ export const AGENT_THREAD_ACTIVE_STATUSES = ["pending", "running"] as const;
13
+ export const AGENT_THREAD_TERMINAL_STATUSES = ["done", "failed", "cancelled"] as const;
14
+
15
+ export const AGENT_THREAD_KINDS = [
16
+ "chat",
17
+ "background",
18
+ "inline",
19
+ "handoff",
20
+ "parallel_branch",
21
+ "scheduled",
22
+ ] as const;
23
+
24
+ export const AGENT_THREAD_PRESENTATION_INTENTS = [
25
+ "background_handoff",
26
+ "inline_result",
27
+ "handoff_speaker",
28
+ "parallel_branch",
29
+ ] as const;
30
+
31
+ export type AgentThreadStatus = (typeof AGENT_THREAD_STATUSES)[number];
32
+ export type AgentThreadActiveStatus =
33
+ (typeof AGENT_THREAD_ACTIVE_STATUSES)[number];
34
+ export type AgentThreadTerminalStatus =
35
+ (typeof AGENT_THREAD_TERMINAL_STATUSES)[number];
36
+ export type AgentThreadKind = (typeof AGENT_THREAD_KINDS)[number];
37
+ export type AgentThreadPresentationIntent =
38
+ (typeof AGENT_THREAD_PRESENTATION_INTENTS)[number];
39
+
40
+ export type AgentThreadListSection = "running" | "future" | "recent";
41
+
42
+ export type AgentThreadSubjectRef = {
43
+ kind: string;
44
+ id: string;
45
+ role?: string;
46
+ };
47
+
48
+ export type AgentThreadSchedule =
49
+ | {
50
+ kind: "once";
51
+ timezone?: string;
52
+ nextRunAt: number;
53
+ }
54
+ | {
55
+ kind: "cron";
56
+ timezone?: string;
57
+ expression: string;
58
+ nextRunAt: number;
59
+ }
60
+ | {
61
+ kind: "interval";
62
+ timezone?: string;
63
+ everyMs: number;
64
+ nextRunAt: number;
65
+ };
66
+
67
+ export type AgentThreadEvidence = {
68
+ kind: string;
69
+ summary?: string;
70
+ refs?: AgentThreadSubjectRef[];
71
+ data?: Record<string, unknown>;
72
+ };
73
+
74
+ export type AgentThreadRuntimeCheckpoint = {
75
+ status?: string;
76
+ toolCallCount?: number;
77
+ traceSummary?: unknown;
78
+ lastToolNames?: string[];
79
+ lastAssistantText?: string;
80
+ evidence?: AgentThreadEvidence[];
81
+ policyState?: unknown;
82
+ runtimeBinding?: unknown;
83
+ errorMessage?: string;
84
+ };
85
+
86
+ export type AgentThread = {
87
+ threadId: string;
88
+ title?: string;
89
+ summary?: string;
90
+
91
+ primaryAgentKey: string;
92
+ agentKeys: string[];
93
+ userId: string;
94
+
95
+ status: AgentThreadStatus;
96
+ threadKind: AgentThreadKind;
97
+ presentationIntent?: AgentThreadPresentationIntent;
98
+
99
+ parentThreadId?: string;
100
+ rootThreadId?: string;
101
+
102
+ dialogId?: string;
103
+ dialogKey?: string;
104
+
105
+ subjectRefs?: AgentThreadSubjectRef[];
106
+
107
+ schedule?: AgentThreadSchedule;
108
+ lastRunThreadId?: string;
109
+
110
+ createdAt: number;
111
+ updatedAt: number;
112
+ startedAt?: number;
113
+ finishedAt?: number;
114
+
115
+ runtimeCheckpoint?: AgentThreadRuntimeCheckpoint;
116
+ };
117
+
118
+ export function buildAgentThreadKey(args: {
119
+ userId: string;
120
+ threadId: string;
121
+ }): string {
122
+ return [AGENT_THREAD_RECORD_PREFIX, args.userId, args.threadId].join("-");
123
+ }
124
+
125
+ export function buildAgentThreadUserRange(userId: string): {
126
+ gte: string;
127
+ lte: string;
128
+ } {
129
+ const start = `${AGENT_THREAD_RECORD_PREFIX}-${userId}-`;
130
+ return { gte: start, lte: `${start}\uffff` };
131
+ }
132
+
133
+ export function buildAgentThreadByAgentStatusIndexKey(args: {
134
+ userId: string;
135
+ primaryAgentKey: string;
136
+ status: AgentThreadStatus;
137
+ threadId: string;
138
+ }): string {
139
+ return [
140
+ AGENT_THREAD_INDEX_PREFIX,
141
+ args.userId,
142
+ "agent",
143
+ args.primaryAgentKey,
144
+ "status",
145
+ args.status,
146
+ args.threadId,
147
+ ].join("-");
148
+ }
149
+
150
+ export function buildAgentThreadByAgentStatusRange(args: {
151
+ userId: string;
152
+ primaryAgentKey: string;
153
+ status: AgentThreadStatus;
154
+ }): {
155
+ gte: string;
156
+ lte: string;
157
+ } {
158
+ const start = [
159
+ AGENT_THREAD_INDEX_PREFIX,
160
+ args.userId,
161
+ "agent",
162
+ args.primaryAgentKey,
163
+ "status",
164
+ args.status,
165
+ "",
166
+ ].join("-");
167
+ return { gte: start, lte: `${start}\uffff` };
168
+ }
169
+
170
+ export function isAgentThreadActiveStatus(
171
+ status: string | null | undefined,
172
+ ): status is AgentThreadActiveStatus {
173
+ return (AGENT_THREAD_ACTIVE_STATUSES as readonly string[]).includes(
174
+ status ?? "",
175
+ );
176
+ }
177
+
178
+ export function isAgentThreadTerminalStatus(
179
+ status: string | null | undefined,
180
+ ): status is AgentThreadTerminalStatus {
181
+ return (AGENT_THREAD_TERMINAL_STATUSES as readonly string[]).includes(
182
+ status ?? "",
183
+ );
184
+ }
185
+
186
+ export function isFutureAgentThread(
187
+ thread: Pick<AgentThread, "schedule" | "status">,
188
+ nowMs: number,
189
+ ): boolean {
190
+ return (
191
+ isAgentThreadActiveStatus(thread.status) &&
192
+ typeof thread.schedule?.nextRunAt === "number" &&
193
+ thread.schedule.nextRunAt > nowMs
194
+ );
195
+ }
196
+
197
+ export function getAgentThreadListSection(
198
+ thread: Pick<AgentThread, "schedule" | "status">,
199
+ nowMs: number,
200
+ ): AgentThreadListSection {
201
+ if (isFutureAgentThread(thread, nowMs)) return "future";
202
+ if (isAgentThreadActiveStatus(thread.status)) return "running";
203
+ return "recent";
204
+ }
205
+
206
+ export function getAgentThreadRootId(
207
+ thread: Pick<AgentThread, "threadId" | "parentThreadId" | "rootThreadId">,
208
+ ): string {
209
+ return thread.rootThreadId || thread.parentThreadId || thread.threadId;
210
+ }
211
+
212
+ export function buildChildAgentThreadRelations(
213
+ parent: Pick<AgentThread, "threadId" | "rootThreadId">,
214
+ ): {
215
+ parentThreadId: string;
216
+ rootThreadId: string;
217
+ } {
218
+ return {
219
+ parentThreadId: parent.threadId,
220
+ rootThreadId: parent.rootThreadId || parent.threadId,
221
+ };
222
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Default maxConcurrent used when an agent has no admission config.
3
+ * v0 policy: hard-coded to 2 until per-agent config is widely adopted.
4
+ */
5
+ export const DEFAULT_AGENT_THREAD_MAX_CONCURRENT = 2;
6
+
7
+ export type AgentThreadAdmissionConfig = {
8
+ maxConcurrent?: unknown;
9
+ };
10
+
11
+ export type AgentThreadAdmissionAgentConfig = {
12
+ maxConcurrent?: unknown;
13
+ admission?: AgentThreadAdmissionConfig | null;
14
+ } | null | undefined;
15
+
16
+ export type AgentThreadAdmissionDecision =
17
+ | {
18
+ allowed: true;
19
+ activeThreadCount: number;
20
+ maxConcurrent: number;
21
+ }
22
+ | {
23
+ allowed: false;
24
+ reason: "max_concurrent_reached";
25
+ activeThreadCount: number;
26
+ maxConcurrent: number;
27
+ };
28
+
29
+ export function normalizeAgentThreadMaxConcurrent(value: unknown): number | null {
30
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
31
+ return null;
32
+ }
33
+ return value;
34
+ }
35
+
36
+ export function resolveAgentThreadMaxConcurrent(
37
+ agentConfig: AgentThreadAdmissionAgentConfig,
38
+ ): number | null {
39
+ const nestedLimit = normalizeAgentThreadMaxConcurrent(
40
+ agentConfig?.admission?.maxConcurrent,
41
+ );
42
+ if (nestedLimit != null) return nestedLimit;
43
+ return normalizeAgentThreadMaxConcurrent(agentConfig?.maxConcurrent);
44
+ }
45
+
46
+ export function decideAgentThreadAdmission(input: {
47
+ agentConfig: AgentThreadAdmissionAgentConfig;
48
+ activeThreadCount: number;
49
+ }): AgentThreadAdmissionDecision {
50
+ const activeThreadCount =
51
+ typeof input.activeThreadCount === "number" &&
52
+ Number.isFinite(input.activeThreadCount) &&
53
+ input.activeThreadCount > 0
54
+ ? Math.floor(input.activeThreadCount)
55
+ : 0;
56
+ const maxConcurrent =
57
+ resolveAgentThreadMaxConcurrent(input.agentConfig) ??
58
+ DEFAULT_AGENT_THREAD_MAX_CONCURRENT;
59
+
60
+ if (activeThreadCount < maxConcurrent) {
61
+ return {
62
+ allowed: true,
63
+ activeThreadCount,
64
+ maxConcurrent,
65
+ };
66
+ }
67
+
68
+ return {
69
+ allowed: false,
70
+ reason: "max_concurrent_reached",
71
+ activeThreadCount,
72
+ maxConcurrent,
73
+ };
74
+ }
@@ -10,6 +10,11 @@ type DialogWriteOp = {
10
10
  key: string;
11
11
  value: DialogRecord;
12
12
  };
13
+ type DialogSubjectRef = {
14
+ kind: string;
15
+ id: string;
16
+ role?: string;
17
+ };
13
18
 
14
19
  function extractLastUserText(messages: AgentRuntimeChatMessage[]) {
15
20
  const lastUser = [...messages].reverse().find((message) => message.role === "user");
@@ -38,6 +43,41 @@ function normalizeNonEmptyString(value: unknown) {
38
43
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
39
44
  }
40
45
 
46
+ function normalizeSubjectRef(ref: unknown): DialogSubjectRef | null {
47
+ if (!ref || typeof ref !== "object") return null;
48
+ const raw = ref as Record<string, unknown>;
49
+ const kind = normalizeNonEmptyString(raw.kind);
50
+ const id = normalizeNonEmptyString(raw.id);
51
+ if (!kind || !id) return null;
52
+ const role = normalizeNonEmptyString(raw.role);
53
+ return {
54
+ kind,
55
+ id,
56
+ ...(role ? { role } : {}),
57
+ };
58
+ }
59
+
60
+ function mergeSubjectRefs(...groups: unknown[]): DialogSubjectRef[] | undefined {
61
+ const refs: DialogSubjectRef[] = [];
62
+ const seen = new Set<string>();
63
+ for (const group of groups) {
64
+ if (!Array.isArray(group)) continue;
65
+ for (const item of group) {
66
+ const ref = normalizeSubjectRef(item);
67
+ if (!ref) continue;
68
+ const key = `${ref.kind}:${ref.id}:${ref.role ?? ""}`;
69
+ if (seen.has(key)) continue;
70
+ seen.add(key);
71
+ refs.push(ref);
72
+ }
73
+ }
74
+ return refs.length ? refs : undefined;
75
+ }
76
+
77
+ function buildRuntimeSubjectRefs(runtimeContext?: Record<string, any> | null): DialogSubjectRef[] | undefined {
78
+ return mergeSubjectRefs(runtimeContext?.subjectRefs);
79
+ }
80
+
41
81
  function buildDialogLineageFields(args: {
42
82
  input: AgentRuntimeSaveTurnInput;
43
83
  existingDialog?: DialogRecord | null;
@@ -105,6 +145,7 @@ export function buildAgentRuntimeDialogWritePlan(args: {
105
145
  const dialogId = args.input.continueDialogId || args.createId();
106
146
  const nowIso = new Date(args.now).toISOString();
107
147
  const dialogKey = `dialog-${args.userId}-${dialogId}`;
148
+ const subjectRefs = buildRuntimeSubjectRefs(args.input.runtimeContext);
108
149
  const dialogRecord = {
109
150
  ...(args.existingDialog && typeof args.existingDialog === "object" ? args.existingDialog : {}),
110
151
  id: dialogId,
@@ -126,6 +167,7 @@ export function buildAgentRuntimeDialogWritePlan(args: {
126
167
  usage: args.input.result.usage,
127
168
  ...(normalizeNonEmptyString(args.input.spaceId) ? { spaceId: normalizeNonEmptyString(args.input.spaceId) } : {}),
128
169
  ...(normalizeNonEmptyString(args.input.category) ? { category: normalizeNonEmptyString(args.input.category) } : {}),
170
+ ...(subjectRefs ? { subjectRefs } : {}),
129
171
  ...buildDialogLineageFields({
130
172
  input: args.input,
131
173
  existingDialog: args.existingDialog,
@@ -55,6 +55,7 @@ export type AgentRuntimeSaveTurnInput = {
55
55
  agentKey: string;
56
56
  messages: AgentRuntimeChatMessage[];
57
57
  result: AgentRuntimeResult;
58
+ runtimeContext?: Record<string, any> | null;
58
59
  continueDialogId?: string;
59
60
  spaceId?: string;
60
61
  category?: string;
@@ -32,6 +32,31 @@ export {
32
32
  buildAgentRuntimeAgentLookupKeys,
33
33
  shouldFetchAgentRuntimeRecordRemotely,
34
34
  } from "./agentRecordKeys";
35
+ export {
36
+ DEFAULT_AGENT_THREAD_MAX_CONCURRENT,
37
+ decideAgentThreadAdmission,
38
+ normalizeAgentThreadMaxConcurrent,
39
+ resolveAgentThreadMaxConcurrent,
40
+ } from "./agentThreadAdmission";
41
+ export {
42
+ AGENT_THREAD_ACTIVE_STATUSES,
43
+ AGENT_THREAD_INDEX_PREFIX,
44
+ AGENT_THREAD_KINDS,
45
+ AGENT_THREAD_PRESENTATION_INTENTS,
46
+ AGENT_THREAD_RECORD_PREFIX,
47
+ AGENT_THREAD_STATUSES,
48
+ AGENT_THREAD_TERMINAL_STATUSES,
49
+ buildAgentThreadByAgentStatusIndexKey,
50
+ buildAgentThreadByAgentStatusRange,
51
+ buildAgentThreadKey,
52
+ buildAgentThreadUserRange,
53
+ buildChildAgentThreadRelations,
54
+ getAgentThreadListSection,
55
+ getAgentThreadRootId,
56
+ isAgentThreadActiveStatus,
57
+ isAgentThreadTerminalStatus,
58
+ isFutureAgentThread,
59
+ } from "./agentThread";
35
60
  export { resolveAgentRuntimeConfigFromRecord } from "./agentRecordConfig";
36
61
  export { dialogMessageRecordToAgentRuntimeMessage } from "./dialogMessageRecord";
37
62
  export { buildAgentRuntimeDialogWritePlan } from "./dialogWritePlan";
@@ -126,6 +151,24 @@ export type {
126
151
  RuntimeToolSurfaceResult,
127
152
  RuntimeToolSurfaceVisibility,
128
153
  } from "./runtimeToolSurface";
154
+ export type {
155
+ AgentThreadAdmissionAgentConfig,
156
+ AgentThreadAdmissionConfig,
157
+ AgentThreadAdmissionDecision,
158
+ } from "./agentThreadAdmission";
159
+ export type {
160
+ AgentThread,
161
+ AgentThreadActiveStatus,
162
+ AgentThreadEvidence,
163
+ AgentThreadKind,
164
+ AgentThreadListSection,
165
+ AgentThreadPresentationIntent,
166
+ AgentThreadRuntimeCheckpoint,
167
+ AgentThreadSchedule,
168
+ AgentThreadStatus,
169
+ AgentThreadSubjectRef,
170
+ AgentThreadTerminalStatus,
171
+ } from "./agentThread";
129
172
  export type {
130
173
  AgentRuntimeChatMessage,
131
174
  AgentRuntimeDecision,
@@ -16,12 +16,14 @@ export type LocalAgentTurnInput = {
16
16
  category?: string;
17
17
  inheritedFromDialogKey?: string;
18
18
  parentDialogId?: string;
19
+ runtimeContext?: Record<string, any> | null;
19
20
  timeoutMs?: number;
20
21
  onToolEvent?: (event: LocalAgentToolEvent) => void;
21
22
  };
22
23
 
23
24
  export type LocalAgentTurnResult = AgentRuntimeResult & {
24
25
  dialogId: string;
26
+ turnMessages?: AgentRuntimeChatMessage[];
25
27
  };
26
28
 
27
29
  export type LocalAgentToolEvent = {
@@ -146,6 +148,26 @@ function summarizeToolResult(content: unknown, metadata?: Record<string, unknown
146
148
  return parts.join(" ");
147
149
  }
148
150
 
151
+ function formatToolMessageContent(args: {
152
+ toolName: string;
153
+ content: string;
154
+ metadata?: Record<string, unknown>;
155
+ }) {
156
+ if (
157
+ (
158
+ args.toolName !== "listFiles" &&
159
+ args.toolName !== "globFiles" &&
160
+ args.toolName !== "searchFiles" &&
161
+ args.toolName !== "readFile"
162
+ ) ||
163
+ !args.metadata ||
164
+ Object.keys(args.metadata).length === 0
165
+ ) {
166
+ return args.content;
167
+ }
168
+ return `${args.content}\n\n[tool metadata]\n${JSON.stringify(args.metadata)}`;
169
+ }
170
+
149
171
  function buildMessages(args: {
150
172
  prompt?: string;
151
173
  history: AgentRuntimeChatMessage[];
@@ -269,7 +291,11 @@ export async function runLocalAgentTurn(
269
291
  }
270
292
  messages.push({
271
293
  role: "tool",
272
- content: toolResult.content,
294
+ content: formatToolMessageContent({
295
+ toolName,
296
+ content: toolResult.content,
297
+ metadata: toolResult.metadata,
298
+ }),
273
299
  tool_call_id: toolCall.id,
274
300
  ...(toolResult.metadata ? { tool_result_metadata: toolResult.metadata } : {}),
275
301
  });
@@ -281,14 +307,16 @@ export async function runLocalAgentTurn(
281
307
  role: "assistant",
282
308
  content: result.content,
283
309
  });
310
+ const turnMessages = messages.slice(turnStartIndex);
284
311
  const saved = await input.adapter.saveTurn({
285
312
  agentKey: agentConfig.key,
286
- messages: messages.slice(turnStartIndex),
313
+ messages: turnMessages,
287
314
  result: {
288
315
  ...result,
289
316
  ...(toolCallCount > 0 ? { toolCallCount } : {}),
290
317
  ...((agentConfig as any).toolSurface ? { runtimeToolSurface: (agentConfig as any).toolSurface } : {}),
291
318
  },
319
+ ...(input.runtimeContext ? { runtimeContext: input.runtimeContext } : {}),
292
320
  ...(input.continueDialogId ? { continueDialogId: input.continueDialogId } : {}),
293
321
  ...(input.spaceId ? { spaceId: input.spaceId } : {}),
294
322
  ...(input.category ? { category: input.category } : {}),
@@ -301,5 +329,6 @@ export async function runLocalAgentTurn(
301
329
  ...(toolCallCount > 0 ? { toolCallCount } : {}),
302
330
  ...((agentConfig as any).toolSurface ? { runtimeToolSurface: (agentConfig as any).toolSurface } : {}),
303
331
  dialogId: saved.dialogId,
332
+ turnMessages,
304
333
  };
305
334
  }
@@ -32,6 +32,7 @@ const DEFAULT_LOCAL_TOOLS = new Set([
32
32
  "readFile",
33
33
  "writeFile",
34
34
  "editFile",
35
+ "globFiles",
35
36
  "searchFiles",
36
37
  "execShell",
37
38
  ]);