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
@@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test";
2
2
  import { Writable } from "node:stream";
3
3
 
4
4
  import {
5
- classifyTaskRunReviewStatus,
5
+ classifyTaskContextReviewStatus,
6
6
  findServerPlatformTools,
7
7
  runAgentTurn,
8
8
  } from "./agentRun";
@@ -24,14 +24,14 @@ class CaptureOutput extends Writable {
24
24
 
25
25
  describe("cli agent run client", () => {
26
26
  test("classifies only clear reviewer outcomes", () => {
27
- expect(classifyTaskRunReviewStatus("Review decision: passed")).toBe("passed");
28
- expect(classifyTaskRunReviewStatus("Review decision: needs_changes")).toBe("needs_changes");
29
- expect(classifyTaskRunReviewStatus("Review decision: blocked")).toBe("blocked");
30
- expect(classifyTaskRunReviewStatus("LGTM, no issues found")).toBe("passed");
31
- expect(classifyTaskRunReviewStatus("Changes requested for missing test coverage")).toBe(
27
+ expect(classifyTaskContextReviewStatus("Review decision: passed")).toBe("passed");
28
+ expect(classifyTaskContextReviewStatus("Review decision: needs_changes")).toBe("needs_changes");
29
+ expect(classifyTaskContextReviewStatus("Review decision: blocked")).toBe("blocked");
30
+ expect(classifyTaskContextReviewStatus("LGTM, no issues found")).toBe("passed");
31
+ expect(classifyTaskContextReviewStatus("Changes requested for missing test coverage")).toBe(
32
32
  "needs_changes"
33
33
  );
34
- expect(classifyTaskRunReviewStatus("I looked at the diff and have notes")).toBeUndefined();
34
+ expect(classifyTaskContextReviewStatus("I looked at the diff and have notes")).toBeUndefined();
35
35
  });
36
36
 
37
37
  test("identifies server platform tools that local runtime cannot provide", () => {
@@ -111,18 +111,23 @@ describe("cli agent run client", () => {
111
111
  reason: "connector_execution_failed",
112
112
  code: "connector_disconnected_mid_run",
113
113
  message: "Connector disconnected for machine: machine-win",
114
+ dialogId: "dialog-failed-1",
114
115
  }, { status: 502 }),
115
116
  });
116
117
 
117
- expect(result).toEqual({ exitCode: 1 });
118
+ expect(result).toEqual({ exitCode: 1, dialogId: "dialog-failed-1" });
118
119
  expect(output.text()).toContain("[nolo] Agent request failed: HTTP 502");
119
120
  expect(output.text()).toContain("Connector execution failed");
120
121
  expect(output.text()).toContain("Connector disconnected for machine: machine-win");
121
122
  expect(output.text()).toContain("code=connector_disconnected_mid_run");
122
123
  expect(output.text()).toContain("reason=connector_execution_failed");
124
+ expect(output.text()).toContain("[nolo] failed dialog: dialog-failed-1");
125
+ expect(output.text()).toContain(
126
+ 'nolo agent run agent-win-codex --continue dialog-failed-1 --msg "retry"'
127
+ );
123
128
  });
124
129
 
125
- test("prepends task-run context for HTTP agent runs", async () => {
130
+ test("converts task row context to subjectRefs without legacy prompt or runtime taskRun payload", async () => {
126
131
  const output = new CaptureOutput();
127
132
  const requests: Array<{ body: any }> = [];
128
133
 
@@ -135,10 +140,8 @@ describe("cli agent run client", () => {
135
140
  env: { AUTH_TOKEN: "token-123" },
136
141
  output,
137
142
  runtimeMode: "server",
138
- taskRunContext: {
143
+ taskRowContext: {
139
144
  rowDbKey: "row-b2e06f801f-01TASK",
140
- taskRunId: "taskrun-1",
141
- workItemId: "frontend-filter",
142
145
  artifactIds: ["artifact-1"],
143
146
  },
144
147
  fetchImpl: async (_url, init) => {
@@ -147,22 +150,17 @@ describe("cli agent run client", () => {
147
150
  },
148
151
  });
149
152
 
150
- expect(requests[0]?.body.userInput).toContain("AI-native task-run context:");
151
- expect(requests[0]?.body.userInput).toContain("current task board is only the present observation and validation use case");
152
- expect(requests[0]?.body.userInput).toContain("rowDbKey: row-b2e06f801f-01TASK");
153
- expect(requests[0]?.body.userInput).toContain("workItemId: frontend-filter");
154
- expect(requests[0]?.body.userInput).toContain("User task:\nFix the filter UI");
155
- expect(requests[0]?.body.runtimeContext.taskRun).toEqual({
156
- rowDbKey: "row-b2e06f801f-01TASK",
157
- taskRunId: "taskrun-1",
158
- workItemId: "frontend-filter",
159
- artifactIds: ["artifact-1"],
153
+ expect(requests[0]?.body.userInput).toBe("Fix the filter UI");
154
+ expect(requests[0]?.body.runtimeContext.taskRun).toBeUndefined();
155
+ expect(requests[0]?.body.runtimeContext.subjectRefs).toContainEqual({
156
+ kind: "table-row",
157
+ id: "row-b2e06f801f-01TASK",
158
+ role: "task",
160
159
  });
161
160
  });
162
161
 
163
- test("records task-run completion after an HTTP agent run returns a dialog", async () => {
162
+ test("does not write task-context completion after an HTTP agent run returns a dialog", async () => {
164
163
  const output = new CaptureOutput();
165
- const writebacks: any[] = [];
166
164
 
167
165
  const result = await runAgentTurn({
168
166
  agentName: "frontend-implementer",
@@ -173,12 +171,8 @@ describe("cli agent run client", () => {
173
171
  env: { AUTH_TOKEN: "token-123" },
174
172
  output,
175
173
  runtimeMode: "server",
176
- taskRunContext: {
174
+ taskRowContext: {
177
175
  rowDbKey: "row-b2e06f801f-01TASK",
178
- workItemId: "frontend-filter",
179
- },
180
- taskRunRecorder: async (args) => {
181
- writebacks.push(args);
182
176
  },
183
177
  fetchImpl: async () => {
184
178
  return Response.json({ content: "patched", dialogId: "dialog-1" });
@@ -186,16 +180,6 @@ describe("cli agent run client", () => {
186
180
  });
187
181
 
188
182
  expect(result).toEqual({ exitCode: 0, dialogId: "dialog-1" });
189
- expect(writebacks).toHaveLength(1);
190
- expect(writebacks[0]).toMatchObject({
191
- status: "completed",
192
- dialogId: "dialog-1",
193
- resultSummary: "patched",
194
- });
195
- expect(writebacks[0].options.taskRunContext).toEqual({
196
- rowDbKey: "row-b2e06f801f-01TASK",
197
- workItemId: "frontend-filter",
198
- });
199
183
  });
200
184
 
201
185
  test("sends image inputs as multimodal userInput over HTTP", async () => {
@@ -237,6 +221,7 @@ describe("cli agent run client", () => {
237
221
  category: "manual-checks",
238
222
  inheritedFromDialogKey: "dialog-user-1-dialog-2",
239
223
  parentDialogId: "dialog-2",
224
+ subjectDialogKey: "01SUBJECTDIALOG",
240
225
  background: true,
241
226
  noStream: true,
242
227
  timeoutMs: 600000,
@@ -261,12 +246,45 @@ describe("cli agent run client", () => {
261
246
  timeoutMs: 600000,
262
247
  stream: false,
263
248
  });
249
+ expect(requests[0]?.body.runtimeContext.subjectRefs).toEqual([
250
+ { kind: "dialog", id: "01SUBJECTDIALOG", role: "subject" },
251
+ ]);
264
252
  expect(result).toEqual({ exitCode: 0, dialogId: "dialog-1" });
265
253
  });
266
254
 
267
- test("records background task-run handoff as running instead of completed", async () => {
255
+ test("sends explicit subject refs over HTTP for review evidence", async () => {
256
+ const output = new CaptureOutput();
257
+ const requests: Array<{ body: any }> = [];
258
+
259
+ await runAgentTurn({
260
+ agentName: "reviewer",
261
+ agentKey: "agent-reviewer",
262
+ serverUrl: "https://nolo.chat",
263
+ message: "Review this implementation",
264
+ subjectRefs: [
265
+ { kind: "table-row", id: "row-user-board-task", role: "subject" },
266
+ { kind: "dialog", id: "dialog-impl", role: "review-target" },
267
+ { kind: "external", id: "commit:abc123", role: "commit" },
268
+ ],
269
+ scriptDir: "C:/missing/scripts",
270
+ env: { AUTH_TOKEN: "token-123" },
271
+ output,
272
+ runtimeMode: "server",
273
+ fetchImpl: async (_url, init) => {
274
+ requests.push({ body: JSON.parse(String(init?.body)) });
275
+ return Response.json({ content: "queued", dialogId: "dialog-review" });
276
+ },
277
+ });
278
+
279
+ expect(requests[0]?.body.runtimeContext.subjectRefs).toEqual([
280
+ { kind: "table-row", id: "row-user-board-task", role: "subject" },
281
+ { kind: "dialog", id: "dialog-impl", role: "review-target" },
282
+ { kind: "external", id: "commit:abc123", role: "commit" },
283
+ ]);
284
+ });
285
+
286
+ test("does not write background handoff state", async () => {
268
287
  const output = new CaptureOutput();
269
- const writebacks: any[] = [];
270
288
 
271
289
  const result = await runAgentTurn({
272
290
  agentName: "frontend-implementer",
@@ -278,12 +296,8 @@ describe("cli agent run client", () => {
278
296
  runtimeMode: "server",
279
297
  background: true,
280
298
  noStream: true,
281
- taskRunContext: {
299
+ taskRowContext: {
282
300
  rowDbKey: "row-b2e06f801f-01TASK",
283
- workItemId: "self",
284
- },
285
- taskRunRecorder: async (args) => {
286
- writebacks.push(args);
287
301
  },
288
302
  output,
289
303
  fetchImpl: async () => {
@@ -292,11 +306,6 @@ describe("cli agent run client", () => {
292
306
  });
293
307
 
294
308
  expect(result).toEqual({ exitCode: 0, dialogId: "dialog-bg" });
295
- expect(writebacks).toHaveLength(1);
296
- expect(writebacks[0]).toMatchObject({
297
- status: "running",
298
- dialogId: "dialog-bg",
299
- });
300
309
  });
301
310
 
302
311
  test("runs forced local turns through the injected runtime adapter without HTTP", async () => {
@@ -571,12 +580,12 @@ describe("cli agent run client", () => {
571
580
  round: 1,
572
581
  tool: "execShell",
573
582
  summary: expect.stringContaining("exit=0"),
583
+ metadata: { exitCode: 0 },
574
584
  });
575
585
  });
576
586
 
577
- test("passes local tool evidence to task-run writeback", async () => {
587
+ test("does not pass local tool evidence to an external writeback path", async () => {
578
588
  const output = new CaptureOutput();
579
- const writebacks: any[] = [];
580
589
  let completeCalls = 0;
581
590
 
582
591
  const result = await runAgentTurn({
@@ -588,12 +597,8 @@ describe("cli agent run client", () => {
588
597
  env: { AUTH_TOKEN: "token-123" },
589
598
  output,
590
599
  runtimeMode: "local",
591
- taskRunContext: {
600
+ taskRowContext: {
592
601
  rowDbKey: "row-b2e06f801f-01TASK",
593
- workItemId: "self",
594
- },
595
- taskRunRecorder: async (args) => {
596
- writebacks.push(args);
597
602
  },
598
603
  localRuntimeAdapter: {
599
604
  host: "cli",
@@ -630,21 +635,10 @@ describe("cli agent run client", () => {
630
635
  });
631
636
 
632
637
  expect(result).toMatchObject({ exitCode: 0, dialogId: "dialog-local" });
633
- expect(writebacks).toHaveLength(1);
634
- expect(writebacks[0].toolEvidence).toEqual({
635
- toolsUsed: ["readFile"],
636
- summary: {
637
- callCount: 1,
638
- resultCount: 1,
639
- errorCount: 0,
640
- rounds: 1,
641
- },
642
- });
643
638
  });
644
639
 
645
- test("records local background task-run handoff as running instead of completed", async () => {
640
+ test("does not write local background handoff state", async () => {
646
641
  const output = new CaptureOutput();
647
- const writebacks: any[] = [];
648
642
 
649
643
  const result = await runAgentTurn({
650
644
  agentName: "frontend",
@@ -656,12 +650,8 @@ describe("cli agent run client", () => {
656
650
  output,
657
651
  runtimeMode: "local",
658
652
  background: true,
659
- taskRunContext: {
653
+ taskRowContext: {
660
654
  rowDbKey: "row-b2e06f801f-01TASK",
661
- workItemId: "self",
662
- },
663
- taskRunRecorder: async (args) => {
664
- writebacks.push(args);
665
655
  },
666
656
  localRuntimeAdapter: {
667
657
  host: "cli",
@@ -685,12 +675,6 @@ describe("cli agent run client", () => {
685
675
  });
686
676
 
687
677
  expect(result).toEqual({ exitCode: 0, dialogId: "dialog-local-bg" });
688
- expect(writebacks).toHaveLength(1);
689
- expect(writebacks[0]).toMatchObject({
690
- status: "running",
691
- dialogId: "dialog-local-bg",
692
- });
693
- expect(writebacks[0].resultSummary).toBeUndefined();
694
678
  });
695
679
 
696
680
  test("auto mode prefers a working local runtime before HTTP", async () => {
@@ -910,7 +894,7 @@ describe("cli agent run client", () => {
910
894
  prompt: "Implement frontend tasks.",
911
895
  provider: "agy",
912
896
  cliProvider: "agy",
913
- toolNames: ["streamParallelAgents", "taskRun"],
897
+ toolNames: ["streamParallelAgents", "queryTableRows"],
914
898
  }),
915
899
  loadDialogHistory: async () => [],
916
900
  saveTurn: async () => ({ dialogId: "dialog-agy-auto-local" }),
@@ -1116,7 +1100,7 @@ describe("cli agent run client", () => {
1116
1100
  model: "fake-local",
1117
1101
  runtimeToolPolicy: {
1118
1102
  version: 1,
1119
- agentTools: ["queryTableRows", "taskRun"],
1103
+ agentTools: ["queryTableRows", "streamParallelAgents"],
1120
1104
  runtimeTools: ["execShell"],
1121
1105
  },
1122
1106
  }),
@@ -1141,7 +1125,7 @@ describe("cli agent run client", () => {
1141
1125
  expect(httpCalls).toHaveLength(1);
1142
1126
  expect(httpCalls[0]?.body.agentKey).toBe("agent-policy-platform-tools");
1143
1127
  expect(output.text()).toContain("auto runtime: skipping local runtime");
1144
- expect(output.text()).toContain("queryTableRows, taskRun");
1128
+ expect(output.text()).toContain("queryTableRows, streamParallelAgents");
1145
1129
  });
1146
1130
 
1147
1131
  test("auto mode skips known platform agents when local config cannot be read", async () => {