nolo-cli 0.1.19 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/agent-runtime/agentConfigOptions.ts +12 -0
- package/agent-runtime/agentRecordConfig.ts +99 -0
- package/agent-runtime/agentRecordKeys.ts +14 -0
- package/agent-runtime/dialogMessageRecord.ts +16 -0
- package/agent-runtime/dialogWritePlan.ts +130 -0
- package/agent-runtime/hostAdapter.ts +13 -0
- package/agent-runtime/hybridRecordStore.ts +147 -0
- package/agent-runtime/index.ts +69 -0
- package/agent-runtime/localLoop.ts +69 -5
- package/agent-runtime/localToolPolicy.ts +130 -0
- package/agent-runtime/localWorkspaceTools.ts +1532 -0
- package/agent-runtime/openAiCompatibleProvider.ts +70 -0
- package/agent-runtime/openAiCompatibleProviderConfig.ts +38 -0
- package/agent-runtime/platformChatProvider.ts +241 -0
- package/agent-runtime/taskWorkspace.ts +193 -0
- package/agent-runtime/types.ts +1 -0
- package/agent-runtime/workspaceSession.ts +76 -0
- package/agentAliases.ts +37 -0
- package/agentPullCommand.ts +1 -1
- package/agentRunCommand.ts +278 -52
- package/agentRuntimeCommands.ts +354 -164
- package/agentRuntimeLocal.ts +38 -0
- package/ai/agent/agentSlice.ts +10 -0
- package/ai/agent/buildEditingContext.ts +5 -0
- package/ai/agent/buildSystemPrompt.ts +41 -18
- package/ai/agent/canvasEditingContext.ts +49 -0
- package/ai/agent/cliExecutor.ts +15 -4
- package/ai/agent/createAgentSchema.ts +2 -0
- package/ai/agent/executeToolCall.ts +3 -2
- package/ai/agent/hooks/usePublicAgents.ts +6 -0
- package/ai/agent/pageBuilderHandoffRules.ts +75 -0
- package/ai/agent/runAgentClientLoop.ts +4 -1
- package/ai/agent/runtimeGuidance.ts +19 -0
- package/ai/agent/server/fetchPublicAgents.ts +51 -1
- package/ai/agent/streamAgentChatTurn.ts +20 -2
- package/ai/agent/streamAgentChatTurnUtils.ts +60 -16
- package/ai/chat/accumulateToolCallChunks.ts +40 -9
- package/ai/chat/parseApiError.ts +3 -0
- package/ai/chat/sendOpenAICompletionsRequest.native.ts +23 -10
- package/ai/chat/sendOpenAICompletionsRequest.ts +13 -1
- package/ai/chat/updateTotalUsage.ts +26 -9
- package/ai/llm/deepinfra.ts +51 -0
- package/ai/llm/getPricing.ts +6 -0
- package/ai/llm/kimi.ts +2 -0
- package/ai/llm/openrouterModels.ts +0 -135
- package/ai/llm/providers.ts +1 -0
- package/ai/llm/types.ts +8 -0
- package/ai/taskRun/taskRunProtocol.ts +882 -0
- package/ai/token/calculatePrice.ts +30 -0
- package/ai/token/externalToolCost.ts +49 -29
- package/ai/token/prepareTokenUsageData.ts +6 -1
- package/ai/token/serverTokenWriter.ts +4 -2
- package/ai/tools/agent/agentTools.ts +21 -0
- package/ai/tools/agent/presets/appBuilderPreset.ts +7 -0
- package/ai/tools/agent/streamParallelAgentsTool.ts +2 -1
- package/ai/tools/agent/taskRunTool.ts +112 -0
- package/ai/tools/applyEditTool.ts +6 -3
- package/ai/tools/applyLineEditsTool.ts +6 -3
- package/ai/tools/checkEnvTool.ts +14 -9
- package/ai/tools/codeSearchTool.ts +17 -5
- package/ai/tools/execBashTool.ts +33 -29
- package/ai/tools/fetchWebpageSupport.ts +24 -0
- package/ai/tools/fetchWebpageTool.ts +18 -5
- package/ai/tools/index.ts +158 -0
- package/ai/tools/jdProductScraperTool.ts +821 -0
- package/ai/tools/listFilesTool.ts +6 -3
- package/ai/tools/localFilesTool.ts +200 -0
- package/ai/tools/readFileTool.ts +6 -3
- package/ai/tools/searchRepoTool.ts +6 -3
- package/ai/tools/table/rowTools.ts +6 -1
- package/ai/tools/taobaoTmallProductScraperTool.ts +49 -0
- package/ai/tools/toolApiClient.ts +20 -6
- package/ai/tools/wereadGatewayTool.ts +152 -0
- package/ai/tools/writeFileTool.ts +6 -3
- package/client/agentConfigResolver.test.ts +70 -0
- package/client/agentConfigResolver.ts +1 -0
- package/client/agentRun.test.ts +430 -7
- package/client/agentRun.ts +504 -64
- package/client/hybridRecordStore.test.ts +115 -0
- package/client/hybridRecordStore.ts +41 -0
- package/client/localAgentRecords.test.ts +27 -0
- package/client/localAgentRecords.ts +7 -0
- package/client/localDialogRecords.test.ts +124 -0
- package/client/localDialogRecords.ts +30 -0
- package/client/localProviderResolver.test.ts +78 -0
- package/client/localProviderResolver.ts +1 -0
- package/client/localRuntimeAdapter.test.ts +621 -9
- package/client/localRuntimeAdapter.ts +275 -250
- package/client/localRuntimeDryRun.test.ts +116 -0
- package/client/localToolPolicy.ts +8 -81
- package/client/taskRunPrompt.ts +26 -0
- package/client/taskWorktree.ts +8 -0
- package/client/workspaceSession.test.ts +57 -0
- package/client/workspaceSession.ts +11 -0
- package/commandRegistry.ts +23 -6
- package/connectorRunArtifact.ts +121 -0
- package/database/actions/write.ts +16 -2
- package/database/hooks/useUserData.ts +9 -3
- package/database/server/dataHandlers.ts +18 -20
- package/database/server/emailRepository.ts +3 -3
- package/database/server/patch.ts +18 -10
- package/database/server/query.ts +43 -4
- package/database/server/read.ts +24 -38
- package/database/server/recordIdentity.ts +100 -0
- package/database/server/write.ts +21 -25
- package/index.ts +70 -33
- package/machineCommands.ts +318 -144
- package/package.json +4 -1
- package/tableCommands.ts +181 -0
- package/taskRunCommand.ts +265 -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
|
|
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: "
|
|
21
|
+
event: "token_split_ledger_charge_start",
|
|
26
22
|
userId: data.userId,
|
|
27
23
|
cost: data.cost,
|
|
28
24
|
txId,
|
|
29
25
|
});
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
data.
|
|
33
|
-
|
|
34
|
-
|
|
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: "
|
|
35
|
+
event: "token_split_ledger_charge_result",
|
|
38
36
|
userId: data.userId,
|
|
39
37
|
cost: data.cost,
|
|
40
38
|
txId,
|
|
41
|
-
|
|
39
|
+
chargeResult,
|
|
42
40
|
});
|
|
43
41
|
|
|
44
|
-
if (!
|
|
42
|
+
if (!chargeResult.success) {
|
|
45
43
|
logger.warn({
|
|
46
|
-
event: "
|
|
47
|
-
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:
|
|
53
|
+
error: chargeResult.error,
|
|
56
54
|
});
|
|
57
55
|
}
|
|
58
56
|
} catch (error) {
|
|
59
57
|
logger.error({
|
|
60
|
-
event: "
|
|
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",
|
package/database/server/patch.ts
CHANGED
|
@@ -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
|
-
|
|
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) {
|
package/database/server/query.ts
CHANGED
|
@@ -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:
|
|
128
|
-
|
|
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:
|
|
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 });
|
package/database/server/read.ts
CHANGED
|
@@ -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 {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
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
|
|
72
|
-
|
|
75
|
+
const isCommunityShare = (share: any): boolean =>
|
|
76
|
+
share?.meta?.visibility === "community";
|
|
73
77
|
|
|
74
|
-
const
|
|
75
|
-
|
|
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
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
const purchased = await
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
+
};
|
package/database/server/write.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// 文件路径: packages/database/server/write.ts
|
|
2
2
|
|
|
3
|
-
import { logger } from "auth/server/shared";
|
|
4
|
-
import {
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
52
|
-
|
|
53
|
-
...
|
|
54
|
-
|
|
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), {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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] === "
|
|
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);
|