nolo-cli 0.1.18 → 0.1.20

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 (111) hide show
  1. package/README.md +9 -1
  2. package/agent-runtime/agentConfigOptions.ts +12 -0
  3. package/agent-runtime/agentRecordConfig.ts +99 -0
  4. package/agent-runtime/agentRecordKeys.ts +14 -0
  5. package/agent-runtime/dialogMessageRecord.ts +16 -0
  6. package/agent-runtime/dialogWritePlan.ts +130 -0
  7. package/agent-runtime/hostAdapter.ts +14 -0
  8. package/agent-runtime/hybridRecordStore.ts +147 -0
  9. package/agent-runtime/index.ts +69 -0
  10. package/agent-runtime/localLoop.ts +78 -6
  11. package/agent-runtime/localToolPolicy.ts +130 -0
  12. package/agent-runtime/localWorkspaceTools.ts +1532 -0
  13. package/agent-runtime/openAiCompatibleProvider.ts +70 -0
  14. package/agent-runtime/openAiCompatibleProviderConfig.ts +38 -0
  15. package/agent-runtime/platformChatProvider.ts +241 -0
  16. package/agent-runtime/taskWorkspace.ts +193 -0
  17. package/agent-runtime/types.ts +2 -0
  18. package/agent-runtime/workspaceSession.ts +76 -0
  19. package/agentAliases.ts +37 -0
  20. package/agentPullCommand.ts +1 -1
  21. package/agentRunCommand.ts +289 -54
  22. package/agentRuntimeCommands.ts +354 -164
  23. package/agentRuntimeLocal.ts +38 -0
  24. package/ai/agent/agentSlice.ts +10 -0
  25. package/ai/agent/buildEditingContext.ts +5 -0
  26. package/ai/agent/buildSystemPrompt.ts +41 -18
  27. package/ai/agent/canvasEditingContext.ts +49 -0
  28. package/ai/agent/cliExecutor.ts +15 -4
  29. package/ai/agent/createAgentSchema.ts +2 -0
  30. package/ai/agent/executeToolCall.ts +3 -2
  31. package/ai/agent/hooks/usePublicAgents.ts +6 -0
  32. package/ai/agent/pageBuilderHandoffRules.ts +75 -0
  33. package/ai/agent/runAgentClientLoop.ts +4 -1
  34. package/ai/agent/runtimeGuidance.ts +19 -0
  35. package/ai/agent/server/fetchPublicAgents.ts +51 -1
  36. package/ai/agent/streamAgentChatTurn.ts +20 -2
  37. package/ai/agent/streamAgentChatTurnUtils.ts +60 -16
  38. package/ai/chat/accumulateToolCallChunks.ts +40 -9
  39. package/ai/chat/parseApiError.ts +3 -0
  40. package/ai/chat/sendOpenAICompletionsRequest.native.ts +23 -10
  41. package/ai/chat/sendOpenAICompletionsRequest.ts +13 -1
  42. package/ai/chat/updateTotalUsage.ts +26 -9
  43. package/ai/llm/deepinfra.ts +51 -0
  44. package/ai/llm/getPricing.ts +6 -0
  45. package/ai/llm/kimi.ts +2 -0
  46. package/ai/llm/openrouterModels.ts +0 -135
  47. package/ai/llm/providers.ts +1 -0
  48. package/ai/llm/types.ts +8 -0
  49. package/ai/taskRun/taskRunProtocol.ts +823 -0
  50. package/ai/token/calculatePrice.ts +30 -0
  51. package/ai/token/externalToolCost.ts +49 -29
  52. package/ai/token/prepareTokenUsageData.ts +6 -1
  53. package/ai/token/serverTokenWriter.ts +4 -2
  54. package/ai/tools/agent/agentTools.ts +21 -0
  55. package/ai/tools/agent/presets/appBuilderPreset.ts +7 -0
  56. package/ai/tools/agent/streamParallelAgentsTool.ts +2 -1
  57. package/ai/tools/agent/taskRunTool.ts +112 -0
  58. package/ai/tools/applyEditTool.ts +6 -3
  59. package/ai/tools/applyLineEditsTool.ts +6 -3
  60. package/ai/tools/checkEnvTool.ts +14 -9
  61. package/ai/tools/codeSearchTool.ts +17 -5
  62. package/ai/tools/execBashTool.ts +33 -29
  63. package/ai/tools/fetchWebpageSupport.ts +24 -0
  64. package/ai/tools/fetchWebpageTool.ts +18 -5
  65. package/ai/tools/index.ts +158 -0
  66. package/ai/tools/jdProductScraperTool.ts +821 -0
  67. package/ai/tools/listFilesTool.ts +6 -3
  68. package/ai/tools/localFilesTool.ts +200 -0
  69. package/ai/tools/readFileTool.ts +6 -3
  70. package/ai/tools/searchRepoTool.ts +6 -3
  71. package/ai/tools/table/rowTools.ts +6 -1
  72. package/ai/tools/taobaoTmallProductScraperTool.ts +49 -0
  73. package/ai/tools/toolApiClient.ts +20 -6
  74. package/ai/tools/wereadGatewayTool.ts +152 -0
  75. package/ai/tools/writeFileTool.ts +6 -3
  76. package/client/agentConfigResolver.test.ts +70 -0
  77. package/client/agentConfigResolver.ts +1 -0
  78. package/client/agentRun.test.ts +361 -7
  79. package/client/agentRun.ts +449 -63
  80. package/client/hybridRecordStore.test.ts +115 -0
  81. package/client/hybridRecordStore.ts +41 -0
  82. package/client/localAgentRecords.test.ts +27 -0
  83. package/client/localAgentRecords.ts +7 -0
  84. package/client/localDialogRecords.test.ts +124 -0
  85. package/client/localDialogRecords.ts +30 -0
  86. package/client/localProviderResolver.test.ts +78 -0
  87. package/client/localProviderResolver.ts +1 -0
  88. package/client/localRuntimeAdapter.test.ts +813 -20
  89. package/client/localRuntimeAdapter.ts +279 -232
  90. package/client/localRuntimeDryRun.test.ts +116 -0
  91. package/client/localToolPolicy.ts +8 -81
  92. package/client/taskRunPrompt.ts +26 -0
  93. package/client/taskWorktree.ts +8 -0
  94. package/client/workspaceSession.test.ts +57 -0
  95. package/client/workspaceSession.ts +11 -0
  96. package/commandRegistry.ts +23 -6
  97. package/connectorRunArtifact.ts +121 -0
  98. package/database/actions/write.ts +16 -2
  99. package/database/hooks/useUserData.ts +9 -3
  100. package/database/server/dataHandlers.ts +18 -20
  101. package/database/server/emailRepository.ts +3 -3
  102. package/database/server/patch.ts +18 -10
  103. package/database/server/query.ts +43 -4
  104. package/database/server/read.ts +24 -38
  105. package/database/server/recordIdentity.ts +100 -0
  106. package/database/server/write.ts +21 -25
  107. package/index.ts +70 -33
  108. package/machineCommands.ts +318 -144
  109. package/package.json +4 -1
  110. package/tableCommands.ts +181 -0
  111. package/taskRunCommand.ts +237 -0
@@ -1,50 +1,48 @@
1
1
  import { pino } from "pino";
2
2
  import serverDb from "./db";
3
+ import { chargeTokenUsageWithLedger } from "auth/server/tokenUsageBilling";
3
4
 
4
5
  const logger = pino({ name: "dataHandlers" });
5
6
 
6
- // 依赖注入:扣费函数类型,由调用方传入,避免 database → auth 循环依赖
7
- type DeductBalanceFn = (userId: string, amount: number, reason: string, txId?: string) => Promise<{ success: boolean; error?: string }>;
8
-
9
7
  export const handleToken = async (
10
8
  data: any,
11
9
  res: any,
12
10
  userId?: string,
13
11
  customKey?: string,
14
12
  actionUserId?: string,
15
- deductBalance?: DeductBalanceFn,
16
13
  ) => {
17
14
  const isStatsKey = customKey.includes("token-stats");
18
15
  await serverDb.put(customKey, data);
19
16
 
20
- if (!isStatsKey && data.cost && data.cost > 0 && deductBalance) {
17
+ if (!isStatsKey && data.cost && data.cost > 0) {
21
18
  try {
22
- //must change
23
19
  const txId = `token-${customKey}`;
24
20
  logger.info({
25
- event: "token_deduct_start",
21
+ event: "token_split_ledger_charge_start",
26
22
  userId: data.userId,
27
23
  cost: data.cost,
28
24
  txId,
29
25
  });
30
- const deductResult = await deductBalance(
31
- data.userId,
32
- data.cost,
33
- `Token generation cost: ${customKey}`,
34
- txId
35
- );
26
+ const chargeResult = await chargeTokenUsageWithLedger({
27
+ store: serverDb,
28
+ userId: data.userId,
29
+ tokenKey: customKey,
30
+ tokenRecord: data,
31
+ reason: `Token generation cost: ${customKey}`,
32
+ txId,
33
+ });
36
34
  logger.info({
37
- event: "token_deduct_result",
35
+ event: "token_split_ledger_charge_result",
38
36
  userId: data.userId,
39
37
  cost: data.cost,
40
38
  txId,
41
- deductResult,
39
+ chargeResult,
42
40
  });
43
41
 
44
- if (!deductResult.success) {
42
+ if (!chargeResult.success) {
45
43
  logger.warn({
46
- event: "token_deduct_failed",
47
- error: deductResult.error,
44
+ event: "token_split_ledger_charge_failed",
45
+ error: chargeResult.error,
48
46
  userId: data.userId,
49
47
  cost: data.cost,
50
48
  txId,
@@ -52,12 +50,12 @@ export const handleToken = async (
52
50
  // next todo fix it
53
51
  return res.status(402).json({
54
52
  message: "Token usage recorded but payment failed",
55
- error: deductResult.error,
53
+ error: chargeResult.error,
56
54
  });
57
55
  }
58
56
  } catch (error) {
59
57
  logger.error({
60
- event: "token_deduct_error",
58
+ event: "token_split_ledger_charge_error",
61
59
  error: error.message,
62
60
  userId: data.userId,
63
61
  dbKey: customKey,
@@ -406,9 +406,9 @@ const ensureCloudflareEmailRoutingRule = async (
406
406
  const config = resolveCloudflareEmailRoutingConfig();
407
407
  if (!config) return;
408
408
 
409
- const url = `${config.apiBaseUrl.replace(/\/+$/, "")}/zones/${encodeURIComponent(
410
- config.zoneId
411
- )}/email/routing/rules`;
409
+ const url = `${config.apiBaseUrl.replace(/\/+$/, "")}/zones/${encodeURIComponent(
410
+ config.zoneId
411
+ )}/email/routing/rules?per_page=100`;
412
412
  const headers = {
413
413
  Authorization: `Bearer ${config.apiToken}`,
414
414
  "Content-Type": "application/json",
@@ -8,8 +8,9 @@ import {
8
8
  isCybotReadOnlyMutation,
9
9
  } from "./cybotReadonly";
10
10
  import { invalidatePublicAgentsCache } from "ai/agent/server/fetchPublicAgents";
11
- import { resolveWriteAuthority } from "./writeAuthority";
12
- import {
11
+ import { resolveWriteAuthority } from "./writeAuthority";
12
+ import { findIdentityPatchField } from "./recordIdentity";
13
+ import {
13
14
  findActiveLiveShareByTable,
14
15
  patchShareMeta,
15
16
  reconcileReplicaTable,
@@ -96,14 +97,21 @@ export const handlePatch = async (req, res) => {
96
97
  .json({ message: "Bad Request: Missing or invalid changes in body" });
97
98
  }
98
99
 
99
- if (isCybotReadOnlyMutation({ dbKey, dataType: changes?.type })) {
100
- return res.status(403).json({
101
- message: CYBOT_READONLY_MESSAGE,
102
- error: "cybot_readonly",
103
- });
104
- }
105
-
106
- try {
100
+ if (isCybotReadOnlyMutation({ dbKey, dataType: changes?.type })) {
101
+ return res.status(403).json({
102
+ message: CYBOT_READONLY_MESSAGE,
103
+ error: "cybot_readonly",
104
+ });
105
+ }
106
+
107
+ const identityPatchField = findIdentityPatchField(dbKey, changes);
108
+ if (identityPatchField) {
109
+ return res.status(400).json({
110
+ message: `Bad Request: identity field '${identityPatchField}' cannot be patched`,
111
+ });
112
+ }
113
+
114
+ try {
107
115
  // --- 修改:使用 dbKey 变量与数据库交互 ---
108
116
  const exist = await serverDb.get(dbKey);
109
117
  if (!exist) {
@@ -61,6 +61,42 @@ const getComparableTimestamp = (item: BaseItem): number => {
61
61
  return 0;
62
62
  };
63
63
 
64
+ const SUMMARY_FIELDS = [
65
+ "dbKey",
66
+ "id",
67
+ "type",
68
+ "userId",
69
+ "contentKey",
70
+ "appKey",
71
+ "appId",
72
+ "name",
73
+ "title",
74
+ "displayName",
75
+ "createdAt",
76
+ "updatedAt",
77
+ "created",
78
+ "updated_at",
79
+ "deletedAt",
80
+ "pinned",
81
+ "spaceId",
82
+ "fileCategory",
83
+ "mimeType",
84
+ "fileSize",
85
+ "originalName",
86
+ "serverOrigin",
87
+ "customUrl",
88
+ "visibility",
89
+ "deployMode",
90
+ ] as const;
91
+
92
+ const toSummaryRecord = (item: BaseItem): BaseItem => {
93
+ const summary: Record<string, unknown> = {};
94
+ for (const field of SUMMARY_FIELDS) {
95
+ if (field in item) summary[field] = item[field];
96
+ }
97
+ return summary as BaseItem;
98
+ };
99
+
64
100
  async function fetchUserData(
65
101
  types: string | string[],
66
102
  userId: string,
@@ -97,9 +133,10 @@ export const handleQuery = async (req: any, res: any) => {
97
133
  ) {
98
134
  return res.status(400).json({ error: "UserId is invalid or missing" });
99
135
  }
100
- const { type, includeDeleted } = req.body as {
136
+ const { type, includeDeleted, summary } = req.body as {
101
137
  type?: string | string[];
102
138
  includeDeleted?: boolean;
139
+ summary?: boolean;
103
140
  };
104
141
  const requestedTypes = normalizeQueryTypes(type);
105
142
  if (requestedTypes.length === 0) {
@@ -117,6 +154,7 @@ export const handleQuery = async (req: any, res: any) => {
117
154
  (left, right) => getComparableTimestamp(right) - getComparableTimestamp(left)
118
155
  );
119
156
  const result = limit ? sortedData.slice(0, limit) : sortedData;
157
+ const responseData = summary === true ? result.map(toSummaryRecord) : result;
120
158
  if (shouldDebugQuery) {
121
159
  console.log("[db/query]", {
122
160
  userId,
@@ -124,13 +162,14 @@ export const handleQuery = async (req: any, res: any) => {
124
162
  includeDeleted: includeDeleted === true,
125
163
  limit: limit ?? null,
126
164
  total: data.length,
127
- returned: result.length,
128
- sampleKeys: result.slice(0, 10).map((item) =>
165
+ returned: responseData.length,
166
+ summary: summary === true,
167
+ sampleKeys: responseData.slice(0, 10).map((item) =>
129
168
  item.dbKey ?? item.contentKey ?? item.appKey ?? item.appId ?? item.id ?? "unknown"
130
169
  ),
131
170
  });
132
171
  }
133
- return res.status(200).json({ data: { data: result } });
172
+ return res.status(200).json({ data: { data: responseData } });
134
173
  } catch (err) {
135
174
  const message = err instanceof Error ? err.message : "Unknown error";
136
175
  return res.status(500).json({ error: message });
@@ -2,11 +2,15 @@
2
2
 
3
3
  import serverDb, { ensureServerDbOpen } from "./db";
4
4
  import { DB_PREFIX, shareKey } from "database/keys";
5
- import { parseToken, verifyToken } from "auth/token";
6
- import { assertSessionTokenVersion } from "auth/sessionTokenVersion";
7
- import { isTombstoneRecord } from "database/tombstones";
8
- import { userActor } from "auth/actor";
9
- import { authorizeActorRecordAccess } from "./actorAccess";
5
+ import { parseToken, verifyToken } from "auth/token";
6
+ import { assertSessionTokenVersion } from "auth/sessionTokenVersion";
7
+ import { hasActiveEntitlement } from "../../auth/server/productEntitlements";
8
+ import { isTombstoneRecord } from "database/tombstones";
9
+ import { userActor } from "auth/actor";
10
+ import { authorizeActorRecordAccess } from "./actorAccess";
11
+ import {
12
+ resolveSharePaidAccess,
13
+ } from "../../share/paidAccess";
10
14
 
11
15
  const isLevelNotFoundError = (error: any): boolean => {
12
16
  const code = error?.code;
@@ -68,20 +72,11 @@ const getAuthorizedUserId = async (req: any): Promise<string | null> => {
68
72
  }
69
73
  };
70
74
 
71
- const purchaseRecordKey = (buyerId: string, shareToken: string) =>
72
- `doc-purchase-${buyerId}-${shareToken}`;
75
+ const isCommunityShare = (share: any): boolean =>
76
+ share?.meta?.visibility === "community";
73
77
 
74
- const extractShareTokenFromDbKey = (dbKey: string): string | null =>
75
- dbKey.startsWith("share-") ? dbKey.slice("share-".length) : null;
76
-
77
- const isCommunityShare = (share: any): boolean =>
78
- share?.meta?.visibility === "community";
79
-
80
- const isShareOwner = (share: any, userId: string | null): boolean =>
81
- Boolean(userId && share?.meta?.authorId === userId);
82
-
83
- const isPaidShare = (share: any): boolean =>
84
- typeof share?.meta?.price === "number" && share.meta.price > 0;
78
+ const isShareOwner = (share: any, userId: string | null): boolean =>
79
+ Boolean(userId && share?.meta?.authorId === userId);
85
80
 
86
81
  const redactSharePayload = (share: any) => ({
87
82
  ...share,
@@ -93,20 +88,7 @@ const redactSharePayload = (share: any) => ({
93
88
  },
94
89
  });
95
90
 
96
- const hasPurchasedShare = async (
97
- userId: string | null,
98
- shareToken: string | null
99
- ): Promise<boolean> => {
100
- if (!userId || !shareToken) return false;
101
- try {
102
- await serverDb.get(purchaseRecordKey(userId, shareToken));
103
- return true;
104
- } catch {
105
- return false;
106
- }
107
- };
108
-
109
- const toPublicProfile = (profile: any, user: any) => ({
91
+ const toPublicProfile = (profile: any, user: any) => ({
110
92
  nickname:
111
93
  typeof profile?.nickname === "string" && profile.nickname.trim()
112
94
  ? profile.nickname
@@ -251,12 +233,16 @@ export const handleReadSingle = async (req: any) => {
251
233
  );
252
234
  }
253
235
 
254
- if (isPaidShare(result) && !owner) {
255
- const shareToken = extractShareTokenFromDbKey(dbKey);
256
- const purchased = await hasPurchasedShare(authedUserId, shareToken);
257
- if (!purchased) {
258
- return new Response(JSON.stringify(redactSharePayload(result)), {
259
- status: 200,
236
+ const paidAccess = resolveSharePaidAccess(result);
237
+ if (paidAccess.mode === "product" && !owner) {
238
+ const purchased = await hasActiveEntitlement({
239
+ store: serverDb,
240
+ userId: authedUserId,
241
+ productId: paidAccess.productId,
242
+ });
243
+ if (!purchased) {
244
+ return new Response(JSON.stringify(redactSharePayload(result)), {
245
+ status: 200,
260
246
  headers: corsHeaders,
261
247
  });
262
248
  }
@@ -0,0 +1,100 @@
1
+ const SPACE_MEMBER_PREFIX = "space-member-";
2
+
3
+ const RESERVED_IDENTITY_PATCH_FIELDS = new Set([
4
+ "dbKey",
5
+ "ownerId",
6
+ "tenantId",
7
+ "userId",
8
+ ]);
9
+
10
+ const parseSpaceMemberKey = (
11
+ dbKey: string
12
+ ): { userId: string; spaceId: string } | null => {
13
+ if (typeof dbKey !== "string" || !dbKey.startsWith(SPACE_MEMBER_PREFIX)) {
14
+ return null;
15
+ }
16
+
17
+ const rest = dbKey.slice(SPACE_MEMBER_PREFIX.length);
18
+ const lastDash = rest.lastIndexOf("-");
19
+ if (lastDash <= 0 || lastDash === rest.length - 1) {
20
+ return null;
21
+ }
22
+
23
+ return {
24
+ userId: rest.slice(0, lastDash),
25
+ spaceId: rest.slice(lastDash + 1),
26
+ };
27
+ };
28
+
29
+ const parseOwnerFromSegmentedKey = (
30
+ dbKey: string,
31
+ prefixA: string,
32
+ prefixB: string
33
+ ): string | null => {
34
+ const parts = dbKey.split("-");
35
+ if (parts[0] !== prefixA || parts[1] !== prefixB || parts.length < 4) {
36
+ return null;
37
+ }
38
+ return parts[2] || null;
39
+ };
40
+
41
+ export const normalizeRecordIdentityForWrite = <T>(dbKey: string, data: T): T => {
42
+ if (!data || typeof data !== "object") {
43
+ return data;
44
+ }
45
+
46
+ const record = { ...(data as Record<string, any>) };
47
+ const spaceMember = parseSpaceMemberKey(dbKey);
48
+ if (spaceMember) {
49
+ return {
50
+ ...record,
51
+ userId: spaceMember.userId,
52
+ spaceId: spaceMember.spaceId,
53
+ } as T;
54
+ }
55
+
56
+ const spaceSettingUserId = parseOwnerFromSegmentedKey(
57
+ dbKey,
58
+ "space",
59
+ "setting"
60
+ );
61
+ if (spaceSettingUserId) {
62
+ return {
63
+ ...record,
64
+ userId: spaceSettingUserId,
65
+ } as T;
66
+ }
67
+
68
+ const userPrefOwnerId = parseOwnerFromSegmentedKey(dbKey, "user", "pref");
69
+ if (userPrefOwnerId) {
70
+ return {
71
+ ...record,
72
+ userId: userPrefOwnerId,
73
+ ownerId: userPrefOwnerId,
74
+ } as T;
75
+ }
76
+
77
+ return data;
78
+ };
79
+
80
+ export const findIdentityPatchField = (
81
+ dbKey: string,
82
+ changes: unknown
83
+ ): string | null => {
84
+ if (!changes || typeof changes !== "object") {
85
+ return null;
86
+ }
87
+
88
+ const fields = new Set(RESERVED_IDENTITY_PATCH_FIELDS);
89
+ if (parseSpaceMemberKey(dbKey)) {
90
+ fields.add("spaceId");
91
+ }
92
+
93
+ for (const field of fields) {
94
+ if (Object.prototype.hasOwnProperty.call(changes, field)) {
95
+ return field;
96
+ }
97
+ }
98
+
99
+ return null;
100
+ };
@@ -1,8 +1,7 @@
1
1
  // 文件路径: packages/database/server/write.ts
2
2
 
3
- import { logger } from "auth/server/shared";
4
- import { deductUserBalance } from "auth/server/deduct";
5
- import { DataType } from "create/types";
3
+ import { logger } from "auth/server/shared";
4
+ import { DataType } from "create/types";
6
5
  import serverDb, { ensureServerDbOpen } from "./db";
7
6
  import { handleToken, handleCybot } from "./dataHandlers";
8
7
  import { handleTransaction } from "./handleTransaction";
@@ -13,9 +12,10 @@ import {
13
12
  } from "./cybotReadonly";
14
13
  import { invalidatePublicAgentsCache } from "ai/agent/server/fetchPublicAgents";
15
14
  import { emitSpaceMemberAddedNotification } from "server/handlers/notificationEmitter";
16
- import { canWriteRecord } from "./writeAuthority";
17
- import { canWriteSpaceMemberRecord, parseSpaceMemberKey } from "./spaceMemberAuthority";
18
- import {
15
+ import { canWriteRecord } from "./writeAuthority";
16
+ import { canWriteSpaceMemberRecord, parseSpaceMemberKey } from "./spaceMemberAuthority";
17
+ import { normalizeRecordIdentityForWrite } from "./recordIdentity";
18
+ import {
19
19
  bootstrapReplicatedTable,
20
20
  findActiveLiveShareByTable,
21
21
  patchShareMeta,
@@ -144,7 +144,8 @@ export const handleWrite = async (req: any, res: any) => {
144
144
  const { user } = req;
145
145
  const actionUserId = user?.userId;
146
146
 
147
- const { userId, data, customKey, indexKeys } = req.body ?? {};
147
+ const { userId, customKey, indexKeys } = req.body ?? {};
148
+ let { data } = req.body ?? {};
148
149
 
149
150
  // 基本参数校验
150
151
  if (!data || !customKey) {
@@ -158,7 +159,7 @@ export const handleWrite = async (req: any, res: any) => {
158
159
  });
159
160
  }
160
161
 
161
- if (isCybotReadOnlyMutation({ dbKey: customKey, dataType: data.type })) {
162
+ if (isCybotReadOnlyMutation({ dbKey: customKey, dataType: data.type })) {
162
163
  logger.warn({
163
164
  event: "cybot_write_blocked",
164
165
  actionUserId,
@@ -167,11 +168,13 @@ export const handleWrite = async (req: any, res: any) => {
167
168
  });
168
169
  return res.status(403).json({
169
170
  message: CYBOT_READONLY_MESSAGE,
170
- error: "cybot_readonly",
171
- });
172
- }
173
-
174
- const saveUserId = userId || data.userId;
171
+ error: "cybot_readonly",
172
+ });
173
+ }
174
+
175
+ data = normalizeRecordIdentityForWrite(customKey, data);
176
+
177
+ const saveUserId = userId || data.userId;
175
178
  const writeAuthority = canWriteRecord({
176
179
  dbKey: customKey,
177
180
  actionUserId,
@@ -246,10 +249,10 @@ export const handleWrite = async (req: any, res: any) => {
246
249
  break;
247
250
  }
248
251
 
249
- case DataType.TOKEN: {
250
- result = await handleToken(data, res, userId, customKey, actionUserId, deductUserBalance);
251
- break;
252
- }
252
+ case DataType.TOKEN: {
253
+ result = await handleToken(data, res, userId, customKey, actionUserId);
254
+ break;
255
+ }
253
256
 
254
257
  case DataType.CYBOT: {
255
258
  result = await handleCybot(data, res, customKey);
@@ -276,14 +279,7 @@ export const handleWrite = async (req: any, res: any) => {
276
279
  console.log("Creating space with key:", customKey);
277
280
  }
278
281
 
279
- if (isSpaceMemberWrite && !data.userId) {
280
- const parsedMemberKey = parseSpaceMemberKey(customKey);
281
- if (parsedMemberKey) {
282
- data.userId = parsedMemberKey.memberUserId;
283
- }
284
- }
285
-
286
- // Generic author enrichment (fallback if frontend didn't provide)
282
+ // Generic author enrichment (fallback if frontend didn't provide)
287
283
  if (user) {
288
284
  if (!data.authorName) data.authorName = user.name || user.username || "Anonymous";
289
285
  if (!data.authorAvatar) data.authorAvatar = user.avatar;
package/index.ts CHANGED
@@ -7,6 +7,7 @@ import { renderHelpText, resolveCommand } from "./commandRegistry";
7
7
  import { runLoginCommand, runLogoutCommand, runWhoamiCommand } from "./authCommands";
8
8
  import {
9
9
  runAgentBindCurrentCommand,
10
+ runAgentReadCommand,
10
11
  runAgentRuntimeDoctorCommand,
11
12
  runAgentSmokeCurrentCommand,
12
13
  runDoctorRuntimeCommand,
@@ -15,10 +16,12 @@ import { runAgentRunCommand } from "./agentRunCommand";
15
16
  import { runAgentPullCommand } from "./agentPullCommand";
16
17
  import { buildEnvFromProfile, loadProfileConfig } from "./client/profileConfig";
17
18
  import { resolveTuiLaunchMode } from "./runtimeModeArgs";
18
- import {
19
- runMachineConnectCommand,
20
- runMachineStatusCommand,
21
- } from "./machineCommands";
19
+ import {
20
+ runMachineConnectCommand,
21
+ runMachineStatusCommand,
22
+ } from "./machineCommands";
23
+ import { runTaskRunCommand } from "./taskRunCommand";
24
+ import { runTableQueryCommand } from "./tableCommands";
22
25
  import { startTuiWorkspace } from "./tui/readlineWorkspace";
23
26
  import {
24
27
  buildCliDoctorText,
@@ -46,13 +49,15 @@ async function runScript(script: string, forwardedArgs: string[], env: NodeJS.Pr
46
49
  process.exit(exitCode);
47
50
  }
48
51
 
49
- const args = process.argv.slice(2);
50
- const profileEnv = buildEnvFromProfile(loadProfileConfig());
51
- const runtimeEnv = {
52
- ...profileEnv,
53
- ...process.env,
54
- NOLO_CLI_VERSION: packageInfo.version,
55
- };
52
+ const args = process.argv.slice(2);
53
+ const profileEnv = buildEnvFromProfile(loadProfileConfig());
54
+ const explicitServerUrl = process.env.NOLO_SERVER || process.env.NOLO_SERVER_URL || process.env.BASE_URL;
55
+ const runtimeEnv = {
56
+ ...profileEnv,
57
+ ...process.env,
58
+ ...(explicitServerUrl ? { NOLO_SERVER: explicitServerUrl, BASE_URL: explicitServerUrl } : {}),
59
+ NOLO_CLI_VERSION: packageInfo.version,
60
+ };
56
61
 
57
62
  function readOption(args: string[], flag: string) {
58
63
  const index = args.indexOf(flag);
@@ -103,20 +108,34 @@ if (args[0] === "logout") {
103
108
  process.exit(runLogoutCommand());
104
109
  }
105
110
 
106
- if (args[0] === "connect") {
107
- process.exit(await runMachineConnectCommand(args.slice(1), { env: runtimeEnv }));
108
- }
109
-
110
- if (args[0] === "daemon" || looksLikeDaemonShortcut(args)) {
111
- const daemonArgs = args[0] === "daemon" ? args.slice(1) : args;
112
- process.exit(await runMachineConnectCommand(["--ws"], { env: buildDaemonEnv(daemonArgs) }));
113
- }
114
-
115
- if (args[0] === "machine" && args[1] === "status") {
116
- process.exit(await runMachineStatusCommand(args.slice(2), { env: runtimeEnv }));
117
- }
111
+ if (args[0] === "connect") {
112
+ process.exit(await runMachineConnectCommand(args.slice(1), {
113
+ env: runtimeEnv,
114
+ cliEntrypointPath: fileURLToPath(import.meta.url),
115
+ }));
116
+ }
117
+
118
+ if (args[0] === "task-run") {
119
+ process.exit(await runTaskRunCommand(args.slice(1), { env: runtimeEnv }));
120
+ }
121
+
122
+ if (args[0] === "table" && args[1] === "query") {
123
+ process.exit(await runTableQueryCommand(args.slice(2), { env: runtimeEnv }));
124
+ }
125
+
126
+ if (args[0] === "daemon" || looksLikeDaemonShortcut(args)) {
127
+ const daemonArgs = args[0] === "daemon" ? args.slice(1) : args;
128
+ process.exit(await runMachineConnectCommand(["--ws"], {
129
+ env: buildDaemonEnv(daemonArgs),
130
+ cliEntrypointPath: fileURLToPath(import.meta.url),
131
+ }));
132
+ }
118
133
 
119
- if (args[0] === "agent" && args[1] === "bind-current") {
134
+ if (args[0] === "machine" && args[1] === "status") {
135
+ process.exit(await runMachineStatusCommand(args.slice(2), { env: runtimeEnv }));
136
+ }
137
+
138
+ if (args[0] === "agent" && args[1] === "bind-current") {
120
139
  process.exit(await runAgentBindCurrentCommand(args.slice(2), { env: runtimeEnv }));
121
140
  }
122
141
 
@@ -131,10 +150,28 @@ if (args[0] === "agent" && args[1] === "run") {
131
150
  }));
132
151
  }
133
152
 
153
+ if (args[0] === "agent" && args[1] === "chat") {
154
+ process.exit(await runAgentRunCommand(args.slice(2), {
155
+ env: runtimeEnv,
156
+ scriptDir: SCRIPT_DIR,
157
+ }));
158
+ }
159
+
160
+ if (args[0] === "chat") {
161
+ process.exit(await runAgentRunCommand(args.slice(1), {
162
+ env: runtimeEnv,
163
+ scriptDir: SCRIPT_DIR,
164
+ }));
165
+ }
166
+
134
167
  if (args[0] === "agent" && args[1] === "pull") {
135
168
  process.exit(await runAgentPullCommand(args.slice(2), { env: runtimeEnv }));
136
169
  }
137
170
 
171
+ if (args[0] === "agent" && args[1] === "read") {
172
+ process.exit(await runAgentReadCommand(args.slice(2), { env: runtimeEnv }));
173
+ }
174
+
138
175
  if (args[0] === "agent" && args[1] === "runtime-doctor") {
139
176
  process.exit(await runAgentRuntimeDoctorCommand(args.slice(2), { env: runtimeEnv }));
140
177
  }
@@ -170,13 +207,13 @@ if (args[0] === "--help" || args[0] === "-h") {
170
207
  process.exit(0);
171
208
  }
172
209
 
173
- const command = resolveCommand(args);
174
- if (!command) {
175
- console.error(`Unknown command: ${args.join(" ")}`);
176
- console.error("");
177
- console.log(renderHelpText());
210
+ const command = resolveCommand(args);
211
+ if (!command) {
212
+ console.error(`Unknown command: ${args.join(" ")}`);
213
+ console.error("");
214
+ console.log(renderHelpText());
178
215
  process.exit(1);
179
- }
180
-
181
- const forwardedArgs = args.slice(command.path.length);
182
- await runScript(command.script, forwardedArgs, runtimeEnv);
216
+ }
217
+
218
+ const forwardedArgs = [...(command.fixedArgs ?? []), ...args.slice(command.path.length)];
219
+ await runScript(command.script, forwardedArgs, runtimeEnv);