nolo-cli 0.49.1 → 0.50.0
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/{_executeModel-KE4PQSZ3.js → _executeModel-JKEH7ACW.js} +48 -42
- package/{action-IU6ON7ZB.js → action-VSG2IYP3.js} +8 -8
- package/{addContentAction-5V5CCI3I.js → addContentAction-2HJJITDI.js} +8 -7
- package/{addDialogAgentAction-KF3KZZNZ.js → addDialogAgentAction-7BSQVVL5.js} +10 -9
- package/{agentDeleteCommand-Z67TJG3W.js → agentDeleteCommand-SVXKS5SC.js} +30 -28
- package/{agentEmailCommands-7LPIK7JE.js → agentEmailCommands-WN7QBTP3.js} +57 -52
- package/{agentListCommands-5J2IM3IT.js → agentListCommands-2BEBOQIG.js} +42 -33
- package/{agentMachineCommands-YXH6FXUQ.js → agentMachineCommands-WALP3ONU.js} +30 -28
- package/{agentRecordCommands-GZLCDVUS.js → agentRecordCommands-E652SW57.js} +55 -50
- package/{agentRunCommand-WYH5Q54Q.js → agentRunCommand-6VXWXKCS.js} +60 -53
- package/{agentRunControl-4GHHSXFZ.js → agentRunControl-LJVFDJGU.js} +3 -1
- package/{agentRuntimeLocal-A6EF3RUY.js → agentRuntimeLocal-SBG7BDFQ.js} +30 -26
- package/{agentSlice-ARTDF6VD.js → agentSlice-3WYXIGEO.js} +8 -8
- package/{agentSuperviseCommands-VB6BUS5H.js → agentSuperviseCommands-F3RFAL7K.js} +2 -2
- package/{appDeleteCommands-V2B7ZCLC.js → appDeleteCommands-NTUNMEXY.js} +31 -29
- package/{appDeployCommands-AXQU44RD.js → appDeployCommands-DISWXRTL.js} +31 -29
- package/{appDeployStatusCommands-FWGSC2UM.js → appDeployStatusCommands-IKRA6B6F.js} +31 -29
- package/{appGetCommands-67R3RFZK.js → appGetCommands-UMOP2DFV.js} +31 -29
- package/{appListCommands-TTASJPGV.js → appListCommands-ZKFD2AKS.js} +31 -29
- package/{authCommand-QOPOEYEK.js → authCommand-BS3CQC6I.js} +11 -11
- package/{chatQueueReduxAdapter-6CAHUQ4O.js → chatQueueReduxAdapter-A3YH4MT4.js} +24 -21
- package/{chunk-76UA7376.js → chunk-273FJUYI.js} +6 -6
- package/{chunk-7CH6U3FL.js → chunk-2GRGFCF6.js} +2 -4
- package/{chunk-T27IEBNQ.js → chunk-2QDDKFG5.js} +74 -1
- package/{chunk-IRJVFYOH.js → chunk-2TLYVDTN.js} +321 -1100
- package/{chunk-6BYAXNH5.js → chunk-3QOCS5IF.js} +11 -2
- package/chunk-6IGD47IC.js +19 -0
- package/{chunk-GFD7TIZU.js → chunk-6TUT5ZJD.js} +4 -6
- package/chunk-6YTOJY35.js +352 -0
- package/{chunk-B54WOZ77.js → chunk-7H7GQL6V.js} +2 -2
- package/{chunk-OYNDYVSE.js → chunk-B45YNDAE.js} +1 -1
- package/{chunk-UQWFIWHX.js → chunk-BKIDF5LV.js} +1 -1
- package/{chunk-5XCW6RQG.js → chunk-BUBPRCIU.js} +1 -1
- package/chunk-BYXBJQAS.js +0 -0
- package/{chunk-X7S7OFUX.js → chunk-CEVCRRHU.js} +4 -4
- package/{chunk-SP3DPVBH.js → chunk-DEIGUGP6.js} +2 -2
- package/{chunk-V2IPBG5U.js → chunk-DGEPT6FH.js} +1 -1
- package/{chunk-3AOLVADY.js → chunk-DVINR3PL.js} +1 -1
- package/{chunk-NN3AMBY3.js → chunk-DXOELY7S.js} +64 -69
- package/{chunk-OFZPJ3NT.js → chunk-E4MARS3Q.js} +1 -1
- package/{chunk-QUCSAM43.js → chunk-EAYBPWHX.js} +87 -66
- package/{chunk-DQLYEGP7.js → chunk-EGVY3GMT.js} +1 -1
- package/{chunk-2JYSSXLY.js → chunk-EJFW7TRP.js} +5 -26
- package/{chunk-HPZL4IBN.js → chunk-FGRCSLPV.js} +34 -45
- package/{chunk-WL3UOGYO.js → chunk-G4WSLTQF.js} +31 -97
- package/{chunk-HR5XDXYM.js → chunk-G5SQVIFZ.js} +3 -3
- package/{chunk-J3UALFHL.js → chunk-GHE5XMVR.js} +1 -1
- package/{chunk-6SMMK7MD.js → chunk-GKB4AZKV.js} +440 -2981
- package/{chunk-IKSFDJXB.js → chunk-GKNGRBE6.js} +150 -27
- package/{chunk-Q24S4NDF.js → chunk-HQTK2SFT.js} +1 -1
- package/{chunk-GDGIEGHV.js → chunk-IPTZ5TVU.js} +7 -6
- package/{chunk-4RXXP6WD.js → chunk-J7KDNHUO.js} +5 -64
- package/{chunk-QDHZCAH3.js → chunk-KNQ5LYVZ.js} +57 -2
- package/{chunk-VRJUIUIY.js → chunk-KVVNC5V3.js} +2 -2
- package/{chunk-CVT7L2WZ.js → chunk-LVCAWHI5.js} +4 -4
- package/{chunk-WX5E6HZS.js → chunk-N6SLQMSA.js} +6 -6
- package/{chunk-SQTVMYMU.js → chunk-NTZO3KX2.js} +3 -3
- package/{chunk-LGUQHYAL.js → chunk-O5FLGN2L.js} +3 -2
- package/{chunk-WKOEOQCI.js → chunk-OSUZKICS.js} +1600 -190
- package/{chunk-EXDR5D6X.js → chunk-Q2QN2RHG.js} +7 -7
- package/{chunk-L3JWI7WX.js → chunk-QOKAKDPE.js} +15 -11
- package/{chunk-757BGY6Y.js → chunk-RFYXK6KI.js} +1 -1
- package/chunk-RQSN75DW.js +1766 -0
- package/{chunk-HCMFFI2G.js → chunk-S3JNURSO.js} +2 -2
- package/{chunk-YZ2WE6CW.js → chunk-SAJSBPI2.js} +2 -2
- package/{chunk-WHVQA7PJ.js → chunk-SCBBGS2N.js} +23 -5
- package/chunk-SGSJBBKP.js +297 -0
- package/{chunk-GFUOVFQA.js → chunk-TUBNQ3OF.js} +2 -2
- package/{chunk-BANYWEYY.js → chunk-TY3ADTX5.js} +1 -1
- package/chunk-UAD2WT54.js +64 -0
- package/{chunk-SW7RCUGA.js → chunk-UEV5WVQV.js} +3 -3
- package/{chunk-EURF2SV6.js → chunk-UMCMW5R3.js} +51 -34
- package/{chunk-K524MUQC.js → chunk-VFDRZHPM.js} +16 -16
- package/{chunk-5WADL4O7.js → chunk-W22UTILW.js} +3 -3
- package/{chunk-IKTHA3JH.js → chunk-WERPP2S7.js} +2 -2
- package/{chunk-GBADO3SM.js → chunk-WSUHBABG.js} +1 -1
- package/{chunk-U5Z7LFZF.js → chunk-X3OGKR5Q.js} +4 -4
- package/{chunk-AVKUDHJO.js → chunk-X5LRMXTS.js} +10 -10
- package/{chunk-DIQCV4FR.js → chunk-X665UCNM.js} +1 -1
- package/{chunk-HCBQVVFZ.js → chunk-X7P6EJUY.js} +4 -4
- package/{chunk-HHBXCRJS.js → chunk-XOJRZUPF.js} +4 -4
- package/{chunk-MRWGHF56.js → chunk-YDH3FDFQ.js} +12 -356
- package/{chunk-TNPCQNGQ.js → chunk-YEFRCO5N.js} +4 -9
- package/{chunk-RO2IUWZE.js → chunk-YUKTCDVN.js} +1 -1
- package/{chunk-56W233KG.js → chunk-YWY25N4V.js} +1 -1
- package/{chunk-RU5D3B22.js → chunk-ZDS63Y4O.js} +2 -2
- package/chunk-ZJ7UHBJS.js +89 -0
- package/{chunk-SG65C33A.js → chunk-ZLMG6NRV.js} +4 -4
- package/{codingSkills-BYQI6EPT.js → codingSkills-FQPCEIWL.js} +2 -2
- package/contentThunks-J33BZB3E.js +71 -0
- package/{createAgentAutomationAction-7BBF5YXN.js → createAgentAutomationAction-HTMTGX6Q.js} +25 -21
- package/{createDialogAction-B5URPHP4.js → createDialogAction-WYQBSN6A.js} +27 -23
- package/{createPageAction-ISJKM5Q3.js → createPageAction-ICRRK3SA.js} +27 -22
- package/{dbSlice-FWF5F4L7.js → dbSlice-YORO4VL4.js} +6 -6
- package/{deleteFile-NPZ64DFN.js → deleteFile-JYKRT7C3.js} +4 -4
- package/{dialogCommands-XHH4MVA4.js → dialogCommands-FRSIAPKY.js} +31 -29
- package/{dialogSlice-NRAN5X7F.js → dialogSlice-5KPVIAWH.js} +24 -21
- package/{docCreateCommands-FUN2Z7HQ.js → docCreateCommands-DBRJMPDX.js} +30 -28
- package/{docDeleteCommands-OZDPFXPF.js → docDeleteCommands-BCYPLWXN.js} +31 -29
- package/{docReadCommands-FIPK7LE3.js → docReadCommands-L5JX4Q7G.js} +30 -28
- package/{docStore-LUS7SWC3.js → docStore-34NXLCEO.js} +8 -33
- package/{docUpdateCommands-BZ5GKAN4.js → docUpdateCommands-RELG7XJD.js} +30 -28
- package/fetchMessages-KZQVBXKY.js +1 -1
- package/{getPricing-HAU62AFM.js → getPricing-FM7TJZFV.js} +4 -4
- package/{handleSendMessageAction-RA4OD2ZH.js → handleSendMessageAction-MEYK7RPD.js} +25 -22
- package/index.js +64 -64
- package/{machineCommands-WDAYEFBE.js → machineCommands-YSXTOWHQ.js} +59 -54
- package/{machineWsRunDispatch-TPQ2FQVS.js → machineWsRunDispatch-DNAYOPQT.js} +54 -49
- package/memberThunks-KVPBG4V7.js +638 -0
- package/{models-4QF4BJVY.js → models-CW5EL6Z7.js} +2 -2
- package/package.json +2 -3
- package/{read-J7RVVCHH.js → read-NIOV5F7W.js} +4 -4
- package/{readDocTool-G2WBM4IH.js → readDocTool-ULKJ5BEM.js} +1 -1
- package/{readlineWorkspace-HN44KGIF.js → readlineWorkspace-KZ3LPQG2.js} +829 -393
- package/{removeDialogAgentAction-RMPISD42.js → removeDialogAgentAction-JGSCPML3.js} +12 -11
- package/{runAgentClientLoop-ZYUI22EA.js → runAgentClientLoop-O2AUHIJT.js} +46 -40
- package/{setDialogExtraReferencesAction-7BUTHDAL.js → setDialogExtraReferencesAction-CORU64NM.js} +9 -8
- package/{setPrimaryDialogAgentAction-WCYIBGZ4.js → setPrimaryDialogAgentAction-5VS2VKLZ.js} +12 -11
- package/{settingSlice-7NN2MI6Y.js → settingSlice-3GNGTC2T.js} +7 -7
- package/{skillDiscovery-COX56OBO.js → skillDiscovery-CYUCJMAG.js} +3 -3
- package/{spaceCommands-OJ2567D4.js → spaceCommands-OI7RIB2T.js} +30 -28
- package/{streamAgentChatTurn-FNFMMGNN.js → streamAgentChatTurn-TA3PGS6C.js} +61 -54
- package/{switchDialogAgentAction-WGMMQHDI.js → switchDialogAgentAction-LP433X4D.js} +10 -9
- package/{tableCommands-P2TXJ3CF.js → tableCommands-R6WFRDYF.js} +1 -1
- package/{toolApiClient-B2MFXMEG.js → toolApiClient-53U4UDFF.js} +3 -2
- package/{tools-QCDR3UMD.js → tools-6ZPQ75XA.js} +37 -32
- package/{tuiSummaryLlmCaller-LCGT62QC.js → tuiSummaryLlmCaller-YKYQB4ZL.js} +17 -15
- package/{updateDialogTitleAction-Y3OGJWDB.js → updateDialogTitleAction-LRJ7NSOS.js} +26 -22
- package/chunk-EUG45QJR.js +0 -6104
- package/spaceSlice-F7JCOZEF.js +0 -127
- package/{chunk-NQ4N4IGO.js → chunk-CS6TCJ7E.js} +3 -3
|
@@ -0,0 +1,1766 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildSpaceLookup,
|
|
3
|
+
getSpaceContentKeys
|
|
4
|
+
} from "./chunk-ID4G2MAR.js";
|
|
5
|
+
import {
|
|
6
|
+
deleteDbRecordOnServers,
|
|
7
|
+
listUserRecordsFromServers,
|
|
8
|
+
readDbRecordFromServers,
|
|
9
|
+
readLiveDbRecordAfterTombstoneMerge
|
|
10
|
+
} from "./chunk-HQTK2SFT.js";
|
|
11
|
+
import {
|
|
12
|
+
fetchWithTransientRetry
|
|
13
|
+
} from "./chunk-5XTOKQLW.js";
|
|
14
|
+
import {
|
|
15
|
+
DRAIN_EXHAUSTED_USER_MESSAGE,
|
|
16
|
+
isDrainExhaustedResponse
|
|
17
|
+
} from "./chunk-3L2WKC3W.js";
|
|
18
|
+
import {
|
|
19
|
+
buildDialogAttachmentPlan,
|
|
20
|
+
extractFileContentIds
|
|
21
|
+
} from "./chunk-H2G6JXTR.js";
|
|
22
|
+
import {
|
|
23
|
+
isLevelLockError
|
|
24
|
+
} from "./chunk-URMS4XY2.js";
|
|
25
|
+
import {
|
|
26
|
+
resolveLocalDialogDbCandidates,
|
|
27
|
+
resolveServerDbPath
|
|
28
|
+
} from "./chunk-GLKOYSRU.js";
|
|
29
|
+
import {
|
|
30
|
+
toErrorMessage
|
|
31
|
+
} from "./chunk-HNMXYYJR.js";
|
|
32
|
+
import {
|
|
33
|
+
parseUserIdFromAuthToken,
|
|
34
|
+
readOption,
|
|
35
|
+
resolveAuthToken,
|
|
36
|
+
resolveDeleteServerCandidates,
|
|
37
|
+
resolveServerCandidates,
|
|
38
|
+
resolveServerUrl
|
|
39
|
+
} from "./chunk-A7GB5UGH.js";
|
|
40
|
+
import {
|
|
41
|
+
parsePositiveFiniteNumberOrFallback
|
|
42
|
+
} from "./chunk-Y5GCAMGP.js";
|
|
43
|
+
import {
|
|
44
|
+
asRecordOrEmpty
|
|
45
|
+
} from "./chunk-KX6YBIHG.js";
|
|
46
|
+
import {
|
|
47
|
+
asNonEmptyStringArray,
|
|
48
|
+
asTrimmedNonEmptyStringArray
|
|
49
|
+
} from "./chunk-I42VC5KR.js";
|
|
50
|
+
import {
|
|
51
|
+
createClientLogger
|
|
52
|
+
} from "./chunk-QOKAKDPE.js";
|
|
53
|
+
import {
|
|
54
|
+
isLevelNotFoundError
|
|
55
|
+
} from "./chunk-OW765XBU.js";
|
|
56
|
+
import {
|
|
57
|
+
asTrimmedString
|
|
58
|
+
} from "./chunk-3EUW3WEO.js";
|
|
59
|
+
import {
|
|
60
|
+
isRecord
|
|
61
|
+
} from "./chunk-KUG52IAP.js";
|
|
62
|
+
import {
|
|
63
|
+
DEFAULT_LOCAL_API_ORIGIN
|
|
64
|
+
} from "./chunk-4SN6J65J.js";
|
|
65
|
+
import {
|
|
66
|
+
asOptionalTrimmedString
|
|
67
|
+
} from "./chunk-G3SUCT4W.js";
|
|
68
|
+
|
|
69
|
+
// packages/cli/dialogAttachmentCleanup.ts
|
|
70
|
+
function asText(value) {
|
|
71
|
+
return asOptionalTrimmedString(value) ?? null;
|
|
72
|
+
}
|
|
73
|
+
async function readDialogMessages(args) {
|
|
74
|
+
const response = await args.fetchImpl(`${args.serverUrl}/rpc/getConvMsgs`, {
|
|
75
|
+
method: "POST",
|
|
76
|
+
headers: {
|
|
77
|
+
Authorization: `Bearer ${args.authToken}`,
|
|
78
|
+
"Content-Type": "application/json"
|
|
79
|
+
},
|
|
80
|
+
body: JSON.stringify({ dialogId: args.dialogId, limit: 1e4 })
|
|
81
|
+
});
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
throw new Error(`read dialog messages failed on ${args.serverUrl}: HTTP ${response.status}`);
|
|
84
|
+
}
|
|
85
|
+
const payload = await response.json();
|
|
86
|
+
return Array.isArray(payload) ? payload : [];
|
|
87
|
+
}
|
|
88
|
+
async function planDialogAttachmentCleanup(args) {
|
|
89
|
+
const messages = await readDialogMessages({
|
|
90
|
+
authToken: args.authToken,
|
|
91
|
+
dialogId: args.dialogId,
|
|
92
|
+
fetchImpl: args.fetchImpl,
|
|
93
|
+
serverUrl: args.serverUrl
|
|
94
|
+
});
|
|
95
|
+
const fileIds = [...new Set(messages.flatMap((message) => extractFileContentIds(message)))];
|
|
96
|
+
const metadataByFileId = {};
|
|
97
|
+
const metadataReadFailures = [];
|
|
98
|
+
for (const fileId of fileIds) {
|
|
99
|
+
try {
|
|
100
|
+
const result = await readDbRecordFromServers({
|
|
101
|
+
authToken: args.authToken,
|
|
102
|
+
dbKey: `file-id-${fileId}`,
|
|
103
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
104
|
+
fetchImpl: args.fetchImpl,
|
|
105
|
+
serverUrls: args.serverUrls,
|
|
106
|
+
label: `file metadata index ${fileId}`
|
|
107
|
+
});
|
|
108
|
+
const mainKey = asText(result.record?.mainKey);
|
|
109
|
+
if (!mainKey) throw new Error(`file-id index missing mainKey`);
|
|
110
|
+
const metadata = await readDbRecordFromServers({
|
|
111
|
+
authToken: args.authToken,
|
|
112
|
+
dbKey: mainKey,
|
|
113
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
114
|
+
fetchImpl: args.fetchImpl,
|
|
115
|
+
serverUrls: args.serverUrls,
|
|
116
|
+
label: `file metadata ${fileId}`
|
|
117
|
+
});
|
|
118
|
+
metadataByFileId[fileId] = {
|
|
119
|
+
...metadata.record,
|
|
120
|
+
dbKey: asText(metadata.record?.dbKey) ?? mainKey
|
|
121
|
+
};
|
|
122
|
+
} catch (error) {
|
|
123
|
+
const fallbackKey = args.ownerId && !fileId.startsWith("file-") ? `file-${args.ownerId}-${fileId}` : null;
|
|
124
|
+
if (fallbackKey) {
|
|
125
|
+
try {
|
|
126
|
+
const metadata = await readDbRecordFromServers({
|
|
127
|
+
authToken: args.authToken,
|
|
128
|
+
dbKey: fallbackKey,
|
|
129
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
130
|
+
fetchImpl: args.fetchImpl,
|
|
131
|
+
serverUrls: args.serverUrls,
|
|
132
|
+
label: `file metadata ${fileId}`
|
|
133
|
+
});
|
|
134
|
+
metadataByFileId[fileId] = {
|
|
135
|
+
...metadata.record,
|
|
136
|
+
dbKey: asText(metadata.record?.dbKey) ?? fallbackKey
|
|
137
|
+
};
|
|
138
|
+
continue;
|
|
139
|
+
} catch {
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
metadataReadFailures.push({
|
|
143
|
+
fileId,
|
|
144
|
+
error: toErrorMessage(error)
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return buildDialogAttachmentPlan({
|
|
149
|
+
dialogId: args.dialogId,
|
|
150
|
+
ownerId: args.ownerId,
|
|
151
|
+
includeUserOwnedReferenced: args.includeUserOwnedReferenced,
|
|
152
|
+
messages,
|
|
153
|
+
metadataByFileId,
|
|
154
|
+
metadataReadFailures
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
async function deleteDialogAttachmentCandidates(args) {
|
|
158
|
+
const results = [];
|
|
159
|
+
for (const candidate of args.candidates) {
|
|
160
|
+
if (candidate.status !== "delete" || !candidate.fileDbKey) continue;
|
|
161
|
+
const deleteResults = await deleteDbRecordOnServers({
|
|
162
|
+
authToken: args.authToken,
|
|
163
|
+
dbKey: candidate.fileDbKey,
|
|
164
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
165
|
+
fetchImpl: args.fetchImpl,
|
|
166
|
+
serverUrls: args.serverUrls
|
|
167
|
+
});
|
|
168
|
+
results.push({
|
|
169
|
+
fileId: candidate.fileId,
|
|
170
|
+
fileDbKey: candidate.fileDbKey,
|
|
171
|
+
deleteResults
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return results;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// packages/cli/dialogCommands.ts
|
|
178
|
+
import { existsSync } from "node:fs";
|
|
179
|
+
|
|
180
|
+
// packages/core/positiveIntegerOrFallback.ts
|
|
181
|
+
function parsePositiveIntegerOrFallback(value, fallback) {
|
|
182
|
+
const parsed = Number(value);
|
|
183
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// packages/cli/dialogCommands.ts
|
|
187
|
+
var logger = createClientLogger("cli:dialog");
|
|
188
|
+
var DIALOG_ID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/i;
|
|
189
|
+
var DIALOG_PATH_RE = /^\/(?:space\/([^/]+)\/)?dialog-(.+)-([0-9A-HJKMNP-TV-Z]{26})\/?$/i;
|
|
190
|
+
var DEFAULT_DIALOG_LIST_LIMIT = 50;
|
|
191
|
+
var DEFAULT_DIALOG_QUERY_LIMIT = 50;
|
|
192
|
+
var UNBOUNDED_DIALOG_CAP = 1e5;
|
|
193
|
+
var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
194
|
+
"--limit",
|
|
195
|
+
"--offset",
|
|
196
|
+
"--exclude-dialog",
|
|
197
|
+
"--row-dbkey",
|
|
198
|
+
"--server",
|
|
199
|
+
"--server-url",
|
|
200
|
+
"--space",
|
|
201
|
+
"--space-id",
|
|
202
|
+
"--subject-id",
|
|
203
|
+
"--subject-kind",
|
|
204
|
+
"--subject-role",
|
|
205
|
+
"--token",
|
|
206
|
+
"--machine-key",
|
|
207
|
+
"--user"
|
|
208
|
+
]);
|
|
209
|
+
function hasFlag(args, flag) {
|
|
210
|
+
return args.includes(flag);
|
|
211
|
+
}
|
|
212
|
+
function readFirstPositional(args) {
|
|
213
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
214
|
+
const value = args[index];
|
|
215
|
+
if (VALUE_FLAGS.has(value)) {
|
|
216
|
+
index += 1;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (!value.startsWith("-")) return value;
|
|
220
|
+
}
|
|
221
|
+
return void 0;
|
|
222
|
+
}
|
|
223
|
+
function readAllPositional(args) {
|
|
224
|
+
const positionals = [];
|
|
225
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
226
|
+
const value = args[index];
|
|
227
|
+
if (VALUE_FLAGS.has(value)) {
|
|
228
|
+
index += 1;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (!value.startsWith("-")) {
|
|
232
|
+
positionals.push(value);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return positionals;
|
|
236
|
+
}
|
|
237
|
+
function resolveResultLimit(args, fallback) {
|
|
238
|
+
if (hasFlag(args, "--all")) {
|
|
239
|
+
return { limit: null, unlimited: true, fromDefault: false };
|
|
240
|
+
}
|
|
241
|
+
const raw = readOption(args, "--limit");
|
|
242
|
+
if (!raw) {
|
|
243
|
+
return { limit: fallback, unlimited: false, fromDefault: true };
|
|
244
|
+
}
|
|
245
|
+
const parsed = Number(raw);
|
|
246
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
247
|
+
return { limit: fallback, unlimited: false, fromDefault: true };
|
|
248
|
+
}
|
|
249
|
+
if (parsed === 0) {
|
|
250
|
+
return { limit: null, unlimited: true, fromDefault: false };
|
|
251
|
+
}
|
|
252
|
+
return { limit: Math.floor(parsed), unlimited: false, fromDefault: false };
|
|
253
|
+
}
|
|
254
|
+
function readOffset(args) {
|
|
255
|
+
const raw = readOption(args, "--offset");
|
|
256
|
+
if (!raw) return 0;
|
|
257
|
+
return Math.floor(parsePositiveFiniteNumberOrFallback(raw, 0));
|
|
258
|
+
}
|
|
259
|
+
function writeJsonStream(output, value) {
|
|
260
|
+
if (Array.isArray(value)) {
|
|
261
|
+
output.write("[\n");
|
|
262
|
+
for (let i = 0; i < value.length; i++) {
|
|
263
|
+
const pretty = JSON.stringify(value[i], null, 2);
|
|
264
|
+
const indented = pretty.split("\n").map((line) => ` ${line}`).join("\n");
|
|
265
|
+
output.write(i === 0 ? indented : `,
|
|
266
|
+
${indented}`);
|
|
267
|
+
}
|
|
268
|
+
output.write("\n]\n");
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (isRecord(value)) {
|
|
272
|
+
const record = value;
|
|
273
|
+
const largeArrayKey = ["dialogs", "items", "messages"].find((key) => Array.isArray(record[key]));
|
|
274
|
+
if (largeArrayKey && Array.isArray(record[largeArrayKey]) && record[largeArrayKey].length > 32) {
|
|
275
|
+
const items = record[largeArrayKey];
|
|
276
|
+
output.write("{\n");
|
|
277
|
+
let wrote = false;
|
|
278
|
+
for (const [key, fieldValue] of Object.entries(record)) {
|
|
279
|
+
if (key === largeArrayKey) continue;
|
|
280
|
+
const pretty = JSON.stringify(fieldValue, null, 2).split("\n").map((line, idx) => idx === 0 ? line : ` ${line}`).join("\n");
|
|
281
|
+
output.write(`${wrote ? ",\n" : ""} ${JSON.stringify(key)}: ${pretty}`);
|
|
282
|
+
wrote = true;
|
|
283
|
+
}
|
|
284
|
+
output.write(`${wrote ? ",\n" : ""} ${JSON.stringify(largeArrayKey)}: [
|
|
285
|
+
`);
|
|
286
|
+
for (let i = 0; i < items.length; i++) {
|
|
287
|
+
const pretty = JSON.stringify(items[i], null, 2);
|
|
288
|
+
const indented = pretty.split("\n").map((line) => ` ${line}`).join("\n");
|
|
289
|
+
output.write(i === 0 ? indented : `,
|
|
290
|
+
${indented}`);
|
|
291
|
+
}
|
|
292
|
+
output.write("\n ]\n}\n");
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
output.write(`${JSON.stringify(value, null, 2)}
|
|
297
|
+
`);
|
|
298
|
+
}
|
|
299
|
+
function writeJsonlStream(output, items) {
|
|
300
|
+
for (const item of items) {
|
|
301
|
+
output.write(`${JSON.stringify(item)}
|
|
302
|
+
`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function getDialogIdFromKey(dbKey) {
|
|
306
|
+
const index = dbKey.lastIndexOf("-");
|
|
307
|
+
return index >= 0 ? dbKey.slice(index + 1) : dbKey;
|
|
308
|
+
}
|
|
309
|
+
function resolveDialogInput(rawInput, userId) {
|
|
310
|
+
const raw = rawInput.trim();
|
|
311
|
+
if (raw.startsWith("http://") || raw.startsWith("https://")) {
|
|
312
|
+
const url = new URL(raw);
|
|
313
|
+
const match = url.pathname.match(DIALOG_PATH_RE);
|
|
314
|
+
if (!match) {
|
|
315
|
+
throw new Error(`Unsupported dialog URL path: ${url.pathname}`);
|
|
316
|
+
}
|
|
317
|
+
const [, spaceId, ownerId, dialogId] = match;
|
|
318
|
+
return {
|
|
319
|
+
dbKey: `dialog-${ownerId}-${dialogId}`,
|
|
320
|
+
dialogId,
|
|
321
|
+
ownerId,
|
|
322
|
+
spaceId: spaceId ? decodeURIComponent(spaceId) : null
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
if (raw.startsWith("dialog-")) {
|
|
326
|
+
const dialogId = getDialogIdFromKey(raw);
|
|
327
|
+
return {
|
|
328
|
+
dbKey: raw,
|
|
329
|
+
dialogId,
|
|
330
|
+
ownerId: raw.slice("dialog-".length, Math.max("dialog-".length, raw.length - dialogId.length - 1)),
|
|
331
|
+
spaceId: null
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
if (!DIALOG_ID_RE.test(raw)) {
|
|
335
|
+
throw new Error(`Unsupported dialog id: ${raw}`);
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
dbKey: `dialog-${userId}-${raw}`,
|
|
339
|
+
dialogId: raw,
|
|
340
|
+
ownerId: userId,
|
|
341
|
+
spaceId: null
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function normalizeDialogRecord(record, fallbackDbKey) {
|
|
345
|
+
const dbKey = asOptionalTrimmedString(record?.dbKey) ?? fallbackDbKey ?? "";
|
|
346
|
+
const id = asOptionalTrimmedString(record?.id) ?? (dbKey ? getDialogIdFromKey(dbKey) : "");
|
|
347
|
+
if (!dbKey || !id) return null;
|
|
348
|
+
return {
|
|
349
|
+
id,
|
|
350
|
+
dbKey,
|
|
351
|
+
title: asOptionalTrimmedString(record?.title) ?? asOptionalTrimmedString(record?.taskLabel) ?? "(untitled)",
|
|
352
|
+
status: typeof record?.status === "string" ? record.status : null,
|
|
353
|
+
updatedAt: typeof record?.updatedAt === "string" || typeof record?.updatedAt === "number" ? record.updatedAt : typeof record?.updated_at === "string" || typeof record?.updated_at === "number" ? record.updated_at : null,
|
|
354
|
+
createdAt: typeof record?.createdAt === "string" || typeof record?.createdAt === "number" ? record.createdAt : typeof record?.created === "string" || typeof record?.created === "number" ? record.created : null,
|
|
355
|
+
spaceId: typeof record?.spaceId === "string" && record.spaceId.trim() ? record.spaceId : null,
|
|
356
|
+
triggerType: typeof record?.triggerType === "string" ? record.triggerType : null,
|
|
357
|
+
primaryAgentKey: typeof record?.primaryAgentKey === "string" && record.primaryAgentKey.trim() ? record.primaryAgentKey : null,
|
|
358
|
+
cybots: Array.isArray(record?.cybots) ? record.cybots.filter((agent) => typeof agent === "string") : []
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function sortDialogs(dialogs) {
|
|
362
|
+
return dialogs.sort((a, b) => {
|
|
363
|
+
const ta = a.updatedAt ?? a.createdAt ?? 0;
|
|
364
|
+
const tb = b.updatedAt ?? b.createdAt ?? 0;
|
|
365
|
+
const left = typeof ta === "number" ? ta : Date.parse(String(ta)) || 0;
|
|
366
|
+
const right = typeof tb === "number" ? tb : Date.parse(String(tb)) || 0;
|
|
367
|
+
return right - left;
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
function isScheduledDialog(record) {
|
|
371
|
+
return record.triggerType === "automation_run" || record.triggerType === "scheduled_run" || Boolean(record.parentAutomationKey) || Boolean(record.parentTaskKey);
|
|
372
|
+
}
|
|
373
|
+
function printListUsage(output) {
|
|
374
|
+
output.write(`Usage:
|
|
375
|
+
nolo dialog list [--space <spaceId|spaceUrl>] [--limit ${DEFAULT_DIALOG_LIST_LIMIT}] [--offset <n>] [--all] [--json] [--jsonl] [--ids-only]
|
|
376
|
+
|
|
377
|
+
Options:
|
|
378
|
+
--space <space> List current user's dialogs attached to one space.
|
|
379
|
+
--limit <n> Maximum dialogs to return. Default: ${DEFAULT_DIALOG_LIST_LIMIT}. Use 0 or --all for full dump.
|
|
380
|
+
--offset <n> Skip first n dialogs after sort/filter (paging).
|
|
381
|
+
--all Full dump (same as --limit 0). Prefer --jsonl for large results.
|
|
382
|
+
--include-scheduled Include scheduled automation run dialogs.
|
|
383
|
+
--json Print machine-readable JSON object.
|
|
384
|
+
--jsonl Stream one dialog JSON object per line (lowest memory).
|
|
385
|
+
--ids-only Print only dialog ids.
|
|
386
|
+
--server <url> Override NOLO_SERVER/BASE_URL.
|
|
387
|
+
--token <jwt> Override AUTH_TOKEN.
|
|
388
|
+
|
|
389
|
+
Lists merge global server candidates and ignore newer tombstones.
|
|
390
|
+
Human default is a short summary; use --json/--jsonl for automation.
|
|
391
|
+
`);
|
|
392
|
+
}
|
|
393
|
+
function printDeleteUsage(output) {
|
|
394
|
+
output.write(`Usage:
|
|
395
|
+
nolo dialog delete <dialogId|dialogKey|dialogUrl...> [--yes] [--include-attachments] [--include-referenced-attachments] [--json]
|
|
396
|
+
|
|
397
|
+
Options:
|
|
398
|
+
--yes Actually delete. Without this, the command is a dry-run.
|
|
399
|
+
--include-attachments Also delete files explicitly owned by this dialog/messages.
|
|
400
|
+
--include-referenced-attachments
|
|
401
|
+
Also delete same-account user-owned files referenced by this dialog.
|
|
402
|
+
--json Print machine-readable JSON.
|
|
403
|
+
--server <url> Prefer this server and include it in global deletion.
|
|
404
|
+
--token <jwt> Override AUTH_TOKEN.
|
|
405
|
+
|
|
406
|
+
Deletes are global across the selected/profile server plus known Nolo cluster peers.
|
|
407
|
+
Attachment cleanup is conservative by default. Use --include-referenced-attachments only
|
|
408
|
+
when you intentionally want to remove user-owned files referenced by the dialog too.
|
|
409
|
+
`);
|
|
410
|
+
}
|
|
411
|
+
function printReadUsage(output) {
|
|
412
|
+
output.write(`Usage:
|
|
413
|
+
nolo dialog read <dialogId|dialogKey|dialogUrl> [limit]
|
|
414
|
+
|
|
415
|
+
Options:
|
|
416
|
+
--server <url> Prefer this server when reading by raw dialog id.
|
|
417
|
+
--token <jwt> Override AUTH_TOKEN.
|
|
418
|
+
--user <userId> Override dialog owner for raw dialog ids.
|
|
419
|
+
|
|
420
|
+
Reads dialog metadata and messages as JSON.
|
|
421
|
+
`);
|
|
422
|
+
}
|
|
423
|
+
function printStatusUsage(output) {
|
|
424
|
+
output.write(`Usage:
|
|
425
|
+
nolo dialog status <dialogId|dialogKey|dialogUrl>
|
|
426
|
+
|
|
427
|
+
Options:
|
|
428
|
+
--server <url> Prefer this server when reading by raw dialog id.
|
|
429
|
+
--token <jwt> Override AUTH_TOKEN.
|
|
430
|
+
--user <userId> Override dialog owner for raw dialog ids.
|
|
431
|
+
|
|
432
|
+
Prints compact dialog run status and next-step hints.
|
|
433
|
+
`);
|
|
434
|
+
}
|
|
435
|
+
function printQueryUsage(output) {
|
|
436
|
+
output.write(`Usage:
|
|
437
|
+
nolo dialog query --subject-kind <kind> --subject-id <id> [--subject-role <role>]
|
|
438
|
+
nolo dialog query --row-dbkey <rowDbKey>
|
|
439
|
+
|
|
440
|
+
Options:
|
|
441
|
+
--row-dbkey <dbKey> Convenience target for table-row task subject refs.
|
|
442
|
+
--subject-kind <kind> Generic subject ref kind. Defaults to table-row with --row-dbkey.
|
|
443
|
+
--subject-id <id> Generic subject ref id. Defaults to --row-dbkey.
|
|
444
|
+
--subject-role <role> Optional role included in the query payload.
|
|
445
|
+
--limit <n> Query limit. Default: ${DEFAULT_DIALOG_QUERY_LIMIT}. Use 0 or --all for full dump.
|
|
446
|
+
--all Full dump (same as --limit 0). Prefer --jsonl for large results.
|
|
447
|
+
--exclude-dialog <id> Exclude one dialog id or dbKey from evidence results.
|
|
448
|
+
--allow-empty Exit 0 when no dialogs match.
|
|
449
|
+
--json Print machine-readable JSON object.
|
|
450
|
+
--jsonl Stream one dialog JSON object per line.
|
|
451
|
+
--server <url> Target server. Defaults to NOLO_SERVER/BASE_URL/profile.
|
|
452
|
+
--token <jwt> Override AUTH_TOKEN.
|
|
453
|
+
|
|
454
|
+
Reads dialog evidence by dialog.subjectRefs through the server query endpoint.
|
|
455
|
+
It is read-only and does not write task rows, row-side evidence caches, or retired orchestration metadata.
|
|
456
|
+
`);
|
|
457
|
+
}
|
|
458
|
+
function isHelpArg(value) {
|
|
459
|
+
return value === "-h" || value === "--help";
|
|
460
|
+
}
|
|
461
|
+
function readDialogLimitArg(args, fallback) {
|
|
462
|
+
const explicit = readOption(args, "--limit");
|
|
463
|
+
const positional = args.find(
|
|
464
|
+
(value, index) => index > 0 && /^\d+$/.test(value) && args[index - 1] !== "--limit"
|
|
465
|
+
);
|
|
466
|
+
const raw = explicit ?? positional;
|
|
467
|
+
if (!raw) return fallback;
|
|
468
|
+
return parsePositiveIntegerOrFallback(raw, fallback);
|
|
469
|
+
}
|
|
470
|
+
function readDialogOwnerId(args, env, authToken) {
|
|
471
|
+
return readOption(args, "--user") ?? env.USER_ID ?? parseUserIdFromAuthToken(authToken) ?? "";
|
|
472
|
+
}
|
|
473
|
+
function resolveDialogReadTarget(args, env, authToken) {
|
|
474
|
+
const rawInput = readFirstPositional(args);
|
|
475
|
+
if (!rawInput) return null;
|
|
476
|
+
const preferredServer = readOption(args, "--server") ?? readOption(args, "--server-url");
|
|
477
|
+
const ownerFallback = readDialogOwnerId(args, env, authToken);
|
|
478
|
+
const raw = rawInput.trim();
|
|
479
|
+
if (raw.startsWith("http://") || raw.startsWith("https://")) {
|
|
480
|
+
const url = new URL(raw);
|
|
481
|
+
const match = url.pathname.match(DIALOG_PATH_RE);
|
|
482
|
+
if (!match) throw new Error(`Unsupported dialog URL path: ${url.pathname}`);
|
|
483
|
+
const [, spaceId, ownerId, dialogId] = match;
|
|
484
|
+
const base = preferredServer ?? env.READ_DIALOG_BASE ?? url.origin;
|
|
485
|
+
return {
|
|
486
|
+
base,
|
|
487
|
+
dialogId,
|
|
488
|
+
dialogKey: `dialog-${ownerId}-${dialogId}`,
|
|
489
|
+
userId: ownerId,
|
|
490
|
+
spaceId: spaceId ? decodeURIComponent(spaceId) : void 0
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
if (raw.startsWith("dialog-")) {
|
|
494
|
+
const dialogId = getDialogIdFromKey(raw);
|
|
495
|
+
const ownerId = raw.slice("dialog-".length, Math.max("dialog-".length, raw.length - dialogId.length - 1));
|
|
496
|
+
return {
|
|
497
|
+
base: preferredServer ?? env.READ_DIALOG_BASE ?? resolveServerUrl(args, env),
|
|
498
|
+
dialogId,
|
|
499
|
+
dialogKey: raw,
|
|
500
|
+
userId: ownerId || ownerFallback
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
if (!DIALOG_ID_RE.test(raw)) {
|
|
504
|
+
throw new Error(`Unsupported dialog id: ${raw}`);
|
|
505
|
+
}
|
|
506
|
+
if (!ownerFallback) {
|
|
507
|
+
throw new Error("raw dialog ids require an auth token userId or --user");
|
|
508
|
+
}
|
|
509
|
+
return {
|
|
510
|
+
base: preferredServer ?? env.READ_DIALOG_BASE ?? resolveServerUrl(args, env),
|
|
511
|
+
dialogId: raw,
|
|
512
|
+
dialogKey: `dialog-${ownerFallback}-${raw}`,
|
|
513
|
+
userId: ownerFallback
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
function toToolName(value) {
|
|
517
|
+
return asTrimmedString(value);
|
|
518
|
+
}
|
|
519
|
+
function countByName(values) {
|
|
520
|
+
return values.reduce((acc, value) => {
|
|
521
|
+
acc[value] = (acc[value] ?? 0) + 1;
|
|
522
|
+
return acc;
|
|
523
|
+
}, {});
|
|
524
|
+
}
|
|
525
|
+
function uniq(values) {
|
|
526
|
+
return [...new Set(values.filter(Boolean))];
|
|
527
|
+
}
|
|
528
|
+
function parseJsonObject(raw) {
|
|
529
|
+
if (typeof raw !== "string" || !raw.trim()) return null;
|
|
530
|
+
try {
|
|
531
|
+
const parsed = JSON.parse(raw);
|
|
532
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
533
|
+
} catch {
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
function deriveWrittenFiles(toolMessages) {
|
|
538
|
+
const writeToolNames = /* @__PURE__ */ new Set(["applyEdit", "applyLineEdits", "writeFile", "createDoc", "updateDoc"]);
|
|
539
|
+
const files = [];
|
|
540
|
+
for (const message of toolMessages) {
|
|
541
|
+
const toolName = toToolName(message?.toolName ?? message?.name);
|
|
542
|
+
if (!writeToolNames.has(toolName)) continue;
|
|
543
|
+
const payload = message?.toolPayload ?? {};
|
|
544
|
+
const content = parseJsonObject(message?.content);
|
|
545
|
+
for (const candidate of [payload?.input?.filePath, payload?.response?.filePath, content?.filePath]) {
|
|
546
|
+
const normalized = asTrimmedString(candidate);
|
|
547
|
+
if (normalized) files.push(normalized);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return uniq(files);
|
|
551
|
+
}
|
|
552
|
+
function deriveToolErrors(toolMessages) {
|
|
553
|
+
const errors = [];
|
|
554
|
+
for (const message of toolMessages) {
|
|
555
|
+
const toolName = toToolName(message?.toolName ?? message?.name);
|
|
556
|
+
const payload = message?.toolPayload ?? {};
|
|
557
|
+
const content = parseJsonObject(message?.content);
|
|
558
|
+
if (payload?.status === "failed" || content?.ok === false || content?.applied === false || content?.success === false) {
|
|
559
|
+
errors.push(toolName || "unknown-tool");
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return uniq(errors);
|
|
563
|
+
}
|
|
564
|
+
async function describeReadFailureStatus(res) {
|
|
565
|
+
if (res.status !== 503) return `HTTP ${res.status}`;
|
|
566
|
+
if (await isDrainExhaustedResponse(res)) {
|
|
567
|
+
return `HTTP 503\uFF08${DRAIN_EXHAUSTED_USER_MESSAGE}\uFF09`;
|
|
568
|
+
}
|
|
569
|
+
return "HTTP 503";
|
|
570
|
+
}
|
|
571
|
+
async function readDialogOverHttp(args) {
|
|
572
|
+
const authHeaders = {
|
|
573
|
+
Authorization: `Bearer ${args.authToken}`,
|
|
574
|
+
"Content-Type": "application/json"
|
|
575
|
+
};
|
|
576
|
+
const buildGetConvMsgsBody = () => JSON.stringify({ dialogId: args.dialogId, dialogKey: args.dialogKey, limit: args.limit });
|
|
577
|
+
const readViaBridge = async () => {
|
|
578
|
+
const bridgeRes = await fetchWithTransientRetry(
|
|
579
|
+
args.fetchImpl,
|
|
580
|
+
`${args.base}/api/dialog-read`,
|
|
581
|
+
{
|
|
582
|
+
method: "POST",
|
|
583
|
+
headers: authHeaders,
|
|
584
|
+
body: JSON.stringify({
|
|
585
|
+
dialogKey: args.dialogKey,
|
|
586
|
+
dialogId: args.dialogId,
|
|
587
|
+
limit: args.limit
|
|
588
|
+
})
|
|
589
|
+
}
|
|
590
|
+
);
|
|
591
|
+
if (!bridgeRes.ok) {
|
|
592
|
+
throw Object.assign(new Error(`dialog read bridge failed: ${await describeReadFailureStatus(bridgeRes)}`), {
|
|
593
|
+
status: bridgeRes.status,
|
|
594
|
+
base: args.base
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
const payload = await bridgeRes.json();
|
|
598
|
+
if (!payload?.ok) {
|
|
599
|
+
throw Object.assign(new Error(`dialog read bridge failed: ${payload?.error ?? "unknown error"}`), {
|
|
600
|
+
status: 500,
|
|
601
|
+
base: args.base
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
return {
|
|
605
|
+
meta: payload.meta,
|
|
606
|
+
msgs: payload.msgs,
|
|
607
|
+
source: "http"
|
|
608
|
+
};
|
|
609
|
+
};
|
|
610
|
+
if (args.messagesOnly) {
|
|
611
|
+
const msgsOnlyRes = await fetchWithTransientRetry(
|
|
612
|
+
args.fetchImpl,
|
|
613
|
+
`${args.base}/rpc/getConvMsgs`,
|
|
614
|
+
{
|
|
615
|
+
method: "POST",
|
|
616
|
+
headers: authHeaders,
|
|
617
|
+
body: buildGetConvMsgsBody()
|
|
618
|
+
}
|
|
619
|
+
);
|
|
620
|
+
if (!msgsOnlyRes.ok) {
|
|
621
|
+
if (msgsOnlyRes.status === 401 || msgsOnlyRes.status === 403) return readViaBridge();
|
|
622
|
+
throw Object.assign(new Error(`read dialog messages failed: ${await describeReadFailureStatus(msgsOnlyRes)}`), {
|
|
623
|
+
status: msgsOnlyRes.status,
|
|
624
|
+
base: args.base
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
return { meta: null, msgs: await msgsOnlyRes.json(), source: "http" };
|
|
628
|
+
}
|
|
629
|
+
const metaRes = await fetchWithTransientRetry(
|
|
630
|
+
args.fetchImpl,
|
|
631
|
+
`${args.base}/api/v1/db/read/${encodeURIComponent(args.dialogKey)}`,
|
|
632
|
+
{ headers: { Authorization: `Bearer ${args.authToken}` } }
|
|
633
|
+
);
|
|
634
|
+
if (!metaRes.ok) {
|
|
635
|
+
if (metaRes.status === 401 || metaRes.status === 403) return readViaBridge();
|
|
636
|
+
throw Object.assign(new Error(`read dialog meta failed: ${await describeReadFailureStatus(metaRes)}`), {
|
|
637
|
+
status: metaRes.status,
|
|
638
|
+
base: args.base
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
const meta = await metaRes.json();
|
|
642
|
+
const msgsRes = await fetchWithTransientRetry(args.fetchImpl, `${args.base}/rpc/getConvMsgs`, {
|
|
643
|
+
method: "POST",
|
|
644
|
+
headers: authHeaders,
|
|
645
|
+
body: buildGetConvMsgsBody()
|
|
646
|
+
});
|
|
647
|
+
if (!msgsRes.ok) {
|
|
648
|
+
if (msgsRes.status === 401 || msgsRes.status === 403) return readViaBridge();
|
|
649
|
+
throw Object.assign(new Error(`read dialog messages failed: ${await describeReadFailureStatus(msgsRes)}`), {
|
|
650
|
+
status: msgsRes.status,
|
|
651
|
+
base: args.base
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
return {
|
|
655
|
+
meta,
|
|
656
|
+
msgs: await msgsRes.json(),
|
|
657
|
+
source: "http"
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
async function tryHttpDialogCandidates(args) {
|
|
661
|
+
const attempts = [];
|
|
662
|
+
for (const base of args.bases) {
|
|
663
|
+
try {
|
|
664
|
+
const result = await readDialogOverHttp({ ...args, base });
|
|
665
|
+
attempts.push({ base, ok: true });
|
|
666
|
+
return { ...result, resolvedBase: base, attempts };
|
|
667
|
+
} catch (error) {
|
|
668
|
+
attempts.push({
|
|
669
|
+
base,
|
|
670
|
+
ok: false,
|
|
671
|
+
status: typeof error === "object" && error !== null && "status" in error ? Number(error.status) : void 0,
|
|
672
|
+
message: toErrorMessage(error)
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
throw Object.assign(new Error("All HTTP dialog reads failed"), { attempts });
|
|
677
|
+
}
|
|
678
|
+
function localDialogDbPath() {
|
|
679
|
+
return resolveServerDbPath();
|
|
680
|
+
}
|
|
681
|
+
function localDialogDbCandidates() {
|
|
682
|
+
return resolveLocalDialogDbCandidates();
|
|
683
|
+
}
|
|
684
|
+
function isLevelDbOpenUnavailableError(error) {
|
|
685
|
+
if (isLevelLockError(error)) return true;
|
|
686
|
+
let current = error;
|
|
687
|
+
for (let depth = 0; current != null && depth < 5; depth += 1) {
|
|
688
|
+
if (isLevelLockError(current)) return true;
|
|
689
|
+
current = current.cause;
|
|
690
|
+
}
|
|
691
|
+
return false;
|
|
692
|
+
}
|
|
693
|
+
function describeLocalDialogFailure(error) {
|
|
694
|
+
const attempts = typeof error === "object" && error !== null && Array.isArray(error.localAttempts) ? error.localAttempts : void 0;
|
|
695
|
+
const failure = {
|
|
696
|
+
attempted: true,
|
|
697
|
+
reason: toErrorMessage(error),
|
|
698
|
+
path: localDialogDbPath()
|
|
699
|
+
};
|
|
700
|
+
if (attempts && attempts.length > 0) {
|
|
701
|
+
failure.attempts = attempts;
|
|
702
|
+
failure.paths = [...new Set(attempts.map((attempt) => attempt.path))];
|
|
703
|
+
failure.locked = attempts.some((attempt) => attempt.status === "locked");
|
|
704
|
+
}
|
|
705
|
+
return failure;
|
|
706
|
+
}
|
|
707
|
+
function isExplicitDialogIdMismatch(meta, dialogId) {
|
|
708
|
+
return meta && typeof meta === "object" && "id" in meta && meta.id != null && String(meta.id) !== dialogId;
|
|
709
|
+
}
|
|
710
|
+
async function readDialogFromLocalDb(dialogKey, dialogId, limit, readLocalDialog) {
|
|
711
|
+
const reader = readLocalDialog ?? (await import("./localDialogRead-NQ33HBSI.js")).readDialogFromLocalDb;
|
|
712
|
+
const result = await reader({ dialogKey, dialogId, limit });
|
|
713
|
+
return {
|
|
714
|
+
...result,
|
|
715
|
+
source: "local-db-fallback"
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
async function readLocalDialogAtDbPathDefault(args) {
|
|
719
|
+
if (!existsSync(args.dbPath)) {
|
|
720
|
+
throw Object.assign(
|
|
721
|
+
new Error(`local database directory does not exist: ${args.dbPath}`),
|
|
722
|
+
{ code: "LEVEL_DATABASE_DIR_MISSING" }
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
const [{ Level }, { safeJsonEncoding }, { fetchMessages }] = await Promise.all([
|
|
726
|
+
import("./levelLazyShim-GZBKODGI.js"),
|
|
727
|
+
import("./serverStoreFactory-YMXDF4NI.js"),
|
|
728
|
+
import("./fetchMessages-KZQVBXKY.js")
|
|
729
|
+
]);
|
|
730
|
+
const db = new Level(args.dbPath, {
|
|
731
|
+
valueEncoding: safeJsonEncoding,
|
|
732
|
+
createIfMissing: false
|
|
733
|
+
});
|
|
734
|
+
try {
|
|
735
|
+
await db.open();
|
|
736
|
+
let meta;
|
|
737
|
+
try {
|
|
738
|
+
meta = await db.get(args.dialogKey);
|
|
739
|
+
} catch (error) {
|
|
740
|
+
if (isLevelNotFoundError(error)) return { meta: void 0, msgs: [] };
|
|
741
|
+
throw error;
|
|
742
|
+
}
|
|
743
|
+
const msgs = await fetchMessages(db, args.dialogId, {
|
|
744
|
+
limit: args.limit,
|
|
745
|
+
throwOnError: true
|
|
746
|
+
});
|
|
747
|
+
return { meta, msgs };
|
|
748
|
+
} finally {
|
|
749
|
+
await db.close().catch(() => void 0);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
async function readDialogFromLocalDbWithFallback(args) {
|
|
753
|
+
const candidates = localDialogDbCandidates();
|
|
754
|
+
const attempts = [];
|
|
755
|
+
let missMessage = "local dialog not found";
|
|
756
|
+
let lockedError;
|
|
757
|
+
let genericError;
|
|
758
|
+
const primaryPath = candidates[0];
|
|
759
|
+
try {
|
|
760
|
+
const local = await readDialogFromLocalDb(
|
|
761
|
+
args.dialogKey,
|
|
762
|
+
args.dialogId,
|
|
763
|
+
args.limit,
|
|
764
|
+
args.readLocalDialog
|
|
765
|
+
);
|
|
766
|
+
const localMeta = local.meta;
|
|
767
|
+
if (!localMeta) {
|
|
768
|
+
attempts.push({ path: primaryPath, status: "miss", reason: missMessage });
|
|
769
|
+
} else if (isExplicitDialogIdMismatch(localMeta, args.dialogId)) {
|
|
770
|
+
missMessage = "local dialog id does not match requested dialog";
|
|
771
|
+
attempts.push({ path: primaryPath, status: "miss", reason: missMessage });
|
|
772
|
+
} else {
|
|
773
|
+
return { ...local, source: "local-db-fallback", dbPath: primaryPath, localAttempts: attempts };
|
|
774
|
+
}
|
|
775
|
+
} catch (error) {
|
|
776
|
+
if (!isLevelNotFoundError(error) && !isLevelDbOpenUnavailableError(error)) throw error;
|
|
777
|
+
const locked = isLevelDbOpenUnavailableError(error);
|
|
778
|
+
attempts.push({
|
|
779
|
+
path: primaryPath,
|
|
780
|
+
status: locked ? "locked" : "miss",
|
|
781
|
+
reason: toErrorMessage(error)
|
|
782
|
+
});
|
|
783
|
+
if (locked && !lockedError) lockedError = error;
|
|
784
|
+
}
|
|
785
|
+
for (const dbPath of candidates.slice(1)) {
|
|
786
|
+
if (!existsSync(dbPath)) {
|
|
787
|
+
attempts.push({
|
|
788
|
+
path: dbPath,
|
|
789
|
+
status: "missing",
|
|
790
|
+
reason: `database directory does not exist: ${dbPath}`
|
|
791
|
+
});
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
try {
|
|
795
|
+
const readAt = args.readLocalDialogAtDbPath ?? readLocalDialogAtDbPathDefault;
|
|
796
|
+
const result = await readAt({
|
|
797
|
+
dbPath,
|
|
798
|
+
dialogKey: args.dialogKey,
|
|
799
|
+
dialogId: args.dialogId,
|
|
800
|
+
limit: args.limit
|
|
801
|
+
});
|
|
802
|
+
if (!result?.meta) {
|
|
803
|
+
attempts.push({ path: dbPath, status: "miss", reason: missMessage });
|
|
804
|
+
} else if (isExplicitDialogIdMismatch(result.meta, args.dialogId)) {
|
|
805
|
+
missMessage = "local dialog id does not match requested dialog";
|
|
806
|
+
attempts.push({ path: dbPath, status: "miss", reason: missMessage });
|
|
807
|
+
} else {
|
|
808
|
+
return { ...result, source: "local-db-fallback", dbPath, localAttempts: attempts };
|
|
809
|
+
}
|
|
810
|
+
} catch (error) {
|
|
811
|
+
const locked = isLevelDbOpenUnavailableError(error);
|
|
812
|
+
attempts.push({
|
|
813
|
+
path: dbPath,
|
|
814
|
+
status: locked ? "locked" : "error",
|
|
815
|
+
reason: toErrorMessage(error)
|
|
816
|
+
});
|
|
817
|
+
if (locked && !lockedError) lockedError = error;
|
|
818
|
+
else if (!locked && !genericError) genericError = error;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
const aggregate = (error, code) => {
|
|
822
|
+
const err = error instanceof Error ? error : new Error(toErrorMessage(error));
|
|
823
|
+
Object.assign(err, { localAttempts: attempts });
|
|
824
|
+
if (code) err.code = code;
|
|
825
|
+
return err;
|
|
826
|
+
};
|
|
827
|
+
if (lockedError) throw aggregate(lockedError);
|
|
828
|
+
if (genericError) throw aggregate(genericError);
|
|
829
|
+
throw aggregate(new Error(missMessage), "LEVEL_NOT_FOUND");
|
|
830
|
+
}
|
|
831
|
+
async function readDialogSnapshot(args) {
|
|
832
|
+
const candidateBases = [
|
|
833
|
+
...resolveServerCandidates({ NOLO_SERVER: args.base }, args.base),
|
|
834
|
+
DEFAULT_LOCAL_API_ORIGIN
|
|
835
|
+
].filter((value, index, all) => all.indexOf(value) === index);
|
|
836
|
+
let localFailure;
|
|
837
|
+
try {
|
|
838
|
+
const local = await readDialogFromLocalDbWithFallback({
|
|
839
|
+
dialogKey: args.dialogKey,
|
|
840
|
+
dialogId: args.dialogId,
|
|
841
|
+
limit: args.limit,
|
|
842
|
+
readLocalDialog: args.readLocalDialog,
|
|
843
|
+
readLocalDialogAtDbPath: args.readLocalDialogAtDbPath
|
|
844
|
+
});
|
|
845
|
+
const localMeta = local.meta;
|
|
846
|
+
const localKey = localMeta && typeof localMeta === "object" ? String(localMeta.dbKey ?? localMeta.contentKey ?? "") : "";
|
|
847
|
+
if (localKey && localKey !== args.dialogKey) {
|
|
848
|
+
logger.debug({ dialogKey: args.dialogKey, recordDbKey: localKey }, "local dialog record dbKey differs from storage key");
|
|
849
|
+
}
|
|
850
|
+
return {
|
|
851
|
+
...local,
|
|
852
|
+
resolvedBase: args.base,
|
|
853
|
+
attempts: [],
|
|
854
|
+
candidateBases
|
|
855
|
+
};
|
|
856
|
+
} catch (error) {
|
|
857
|
+
localFailure = describeLocalDialogFailure(error);
|
|
858
|
+
if (!isLevelNotFoundError(error) && !isLevelDbOpenUnavailableError(error)) {
|
|
859
|
+
const enriched = error;
|
|
860
|
+
enriched.dialogId = args.dialogId;
|
|
861
|
+
if (localFailure) enriched.local = localFailure;
|
|
862
|
+
throw enriched;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
try {
|
|
866
|
+
const result = await tryHttpDialogCandidates({
|
|
867
|
+
bases: candidateBases,
|
|
868
|
+
dialogKey: args.dialogKey,
|
|
869
|
+
dialogId: args.dialogId,
|
|
870
|
+
limit: args.limit,
|
|
871
|
+
authToken: args.authToken,
|
|
872
|
+
fetchImpl: args.fetchImpl,
|
|
873
|
+
messagesOnly: args.messagesOnly
|
|
874
|
+
});
|
|
875
|
+
return { ...result, candidateBases };
|
|
876
|
+
} catch (error) {
|
|
877
|
+
const enriched = error;
|
|
878
|
+
enriched.dialogId = args.dialogId;
|
|
879
|
+
if (localFailure) {
|
|
880
|
+
enriched.local = localFailure;
|
|
881
|
+
const attemptedPaths = localFailure.paths ?? [localFailure.path];
|
|
882
|
+
enriched.message = `${enriched.message}; local read attempted (${attemptedPaths.join(" \u2192 ")}): ${localFailure.reason}`;
|
|
883
|
+
}
|
|
884
|
+
throw error;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
function compact(value, max = 180) {
|
|
888
|
+
const text = asTrimmedString(value);
|
|
889
|
+
if (!text) return "";
|
|
890
|
+
return text.length > max ? `${text.slice(0, max - 3)}...` : text;
|
|
891
|
+
}
|
|
892
|
+
function normalizeNonEmptyString(value) {
|
|
893
|
+
return asOptionalTrimmedString(value) ?? null;
|
|
894
|
+
}
|
|
895
|
+
function normalizeSubjectKind(kind) {
|
|
896
|
+
return kind === "tableRow" ? "table-row" : kind;
|
|
897
|
+
}
|
|
898
|
+
function normalizeDialogSubjectRef(value) {
|
|
899
|
+
if (!isRecord(value)) return null;
|
|
900
|
+
const kindRaw = asOptionalTrimmedString(value.kind);
|
|
901
|
+
const kind = kindRaw ? normalizeSubjectKind(kindRaw) : "";
|
|
902
|
+
const id = asOptionalTrimmedString(value.id) ?? "";
|
|
903
|
+
if (!kind || !id) return null;
|
|
904
|
+
const role = asOptionalTrimmedString(value.role) ?? "";
|
|
905
|
+
return {
|
|
906
|
+
kind,
|
|
907
|
+
id,
|
|
908
|
+
...role ? { role } : {}
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
function extractDialogSubjectRefs(dialog) {
|
|
912
|
+
if (!isRecord(dialog) || !Array.isArray(dialog.subjectRefs)) return [];
|
|
913
|
+
return dialog.subjectRefs.map(normalizeDialogSubjectRef).filter((ref) => Boolean(ref));
|
|
914
|
+
}
|
|
915
|
+
function dialogMatchesSubjectRef(dialog, target) {
|
|
916
|
+
const normalizedTarget = normalizeDialogSubjectRef(target);
|
|
917
|
+
if (!normalizedTarget) return false;
|
|
918
|
+
return extractDialogSubjectRefs(dialog).some(
|
|
919
|
+
(ref) => ref.kind === normalizedTarget.kind && ref.id === normalizedTarget.id
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
function hasArtifactEvidence(value) {
|
|
923
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
924
|
+
if (isRecord(value)) return Object.keys(value).length > 0;
|
|
925
|
+
return Boolean(value);
|
|
926
|
+
}
|
|
927
|
+
function artifactEvidenceCount(value) {
|
|
928
|
+
if (Array.isArray(value)) return value.length;
|
|
929
|
+
if (isRecord(value)) return Object.keys(value).length > 0 ? 1 : 0;
|
|
930
|
+
return value ? 1 : 0;
|
|
931
|
+
}
|
|
932
|
+
function summarizeDialogEvidence(dialog, target) {
|
|
933
|
+
const dialogKey = asOptionalTrimmedString(dialog?.dbKey) ?? "";
|
|
934
|
+
const checkpoint = asRecordOrEmpty(dialog?.runtimeCheckpoint);
|
|
935
|
+
const matchedSubjectRefs = extractDialogSubjectRefs(dialog).filter(
|
|
936
|
+
(ref) => ref.kind === normalizeSubjectKind(target.kind) && ref.id === target.id
|
|
937
|
+
);
|
|
938
|
+
const lastToolNames = asNonEmptyStringArray(checkpoint.lastToolNames);
|
|
939
|
+
return {
|
|
940
|
+
dialogId: asOptionalTrimmedString(dialog?.dialogId) ?? asOptionalTrimmedString(dialog?.id) ?? (dialogKey ? getDialogIdFromKey(dialogKey) : null),
|
|
941
|
+
dialogKey: dialogKey || null,
|
|
942
|
+
title: asOptionalTrimmedString(dialog?.title) ?? null,
|
|
943
|
+
status: asOptionalTrimmedString(dialog?.status) ?? null,
|
|
944
|
+
checkpointStatus: asOptionalTrimmedString(checkpoint.status) ?? null,
|
|
945
|
+
updatedAt: typeof dialog?.updatedAt === "string" || typeof dialog?.updatedAt === "number" ? dialog.updatedAt : null,
|
|
946
|
+
hasArtifacts: hasArtifactEvidence(dialog?.artifacts),
|
|
947
|
+
artifactCount: artifactEvidenceCount(dialog?.artifacts),
|
|
948
|
+
subjectRefs: matchedSubjectRefs,
|
|
949
|
+
lastToolNames
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
function verifyDialogSubjectQuery(dialogs, target, allowEmpty) {
|
|
953
|
+
const unmatchedDialogs = dialogs.filter((dialog) => !dialogMatchesSubjectRef(dialog, target)).map((dialog) => ({
|
|
954
|
+
dialogId: asOptionalTrimmedString(dialog?.dialogId) ?? asOptionalTrimmedString(dialog?.id) ?? (typeof dialog?.dbKey === "string" ? getDialogIdFromKey(dialog.dbKey) : null),
|
|
955
|
+
dialogKey: asOptionalTrimmedString(dialog?.dbKey) ?? null,
|
|
956
|
+
subjectRefs: extractDialogSubjectRefs(dialog)
|
|
957
|
+
}));
|
|
958
|
+
const reason = unmatchedDialogs.length > 0 ? "unmatched_results" : dialogs.length === 0 && !allowEmpty ? "empty_results" : "ok";
|
|
959
|
+
return {
|
|
960
|
+
ok: reason === "ok",
|
|
961
|
+
reason,
|
|
962
|
+
target,
|
|
963
|
+
returnedCount: dialogs.length,
|
|
964
|
+
matchedCount: dialogs.length - unmatchedDialogs.length,
|
|
965
|
+
unmatchedCount: unmatchedDialogs.length,
|
|
966
|
+
unmatchedDialogs
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
function normalizeExcludedDialogIds(value) {
|
|
970
|
+
const rawValues = Array.isArray(value) ? value : [value];
|
|
971
|
+
const ids = /* @__PURE__ */ new Set();
|
|
972
|
+
for (const rawValue of rawValues) {
|
|
973
|
+
if (typeof rawValue !== "string") continue;
|
|
974
|
+
const trimmed = rawValue.trim();
|
|
975
|
+
if (!trimmed) continue;
|
|
976
|
+
ids.add(trimmed);
|
|
977
|
+
if (trimmed.startsWith("dialog-")) {
|
|
978
|
+
ids.add(getDialogIdFromKey(trimmed));
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
return [...ids];
|
|
982
|
+
}
|
|
983
|
+
function getDialogIdentityValues(dialog) {
|
|
984
|
+
if (!isRecord(dialog)) return [];
|
|
985
|
+
const values = /* @__PURE__ */ new Set();
|
|
986
|
+
for (const rawValue of [dialog.dialogId, dialog.id, dialog.dbKey]) {
|
|
987
|
+
if (typeof rawValue !== "string") continue;
|
|
988
|
+
const trimmed = rawValue.trim();
|
|
989
|
+
if (!trimmed) continue;
|
|
990
|
+
values.add(trimmed);
|
|
991
|
+
if (trimmed.startsWith("dialog-")) {
|
|
992
|
+
values.add(getDialogIdFromKey(trimmed));
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
return [...values];
|
|
996
|
+
}
|
|
997
|
+
function excludeDialogsById(dialogs, excludeDialogIds) {
|
|
998
|
+
if (excludeDialogIds.length === 0) return dialogs;
|
|
999
|
+
const excluded = new Set(excludeDialogIds);
|
|
1000
|
+
return dialogs.filter(
|
|
1001
|
+
(dialog) => getDialogIdentityValues(dialog).every((value) => !excluded.has(value))
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
async function queryDialogEvidenceBySubjectRef(args) {
|
|
1005
|
+
const res = await args.fetchImpl(
|
|
1006
|
+
`${args.serverUrl}/api/v1/db/query/${encodeURIComponent(args.userId)}`,
|
|
1007
|
+
{
|
|
1008
|
+
method: "POST",
|
|
1009
|
+
headers: {
|
|
1010
|
+
Authorization: `Bearer ${args.authToken}`,
|
|
1011
|
+
"Content-Type": "application/json"
|
|
1012
|
+
},
|
|
1013
|
+
body: JSON.stringify({
|
|
1014
|
+
type: "dialog",
|
|
1015
|
+
limit: args.limit,
|
|
1016
|
+
subjectRef: args.subjectRef
|
|
1017
|
+
})
|
|
1018
|
+
}
|
|
1019
|
+
);
|
|
1020
|
+
const data = await res.json().catch(() => ({}));
|
|
1021
|
+
if (!res.ok) {
|
|
1022
|
+
throw new Error(`dialog subject query failed: HTTP ${res.status} ${JSON.stringify(data)}`);
|
|
1023
|
+
}
|
|
1024
|
+
return Array.isArray(data?.data?.data) ? data.data.data : Array.isArray(data?.data) ? data.data : [];
|
|
1025
|
+
}
|
|
1026
|
+
function renderCompactDialogStatus(snapshot) {
|
|
1027
|
+
const checkpoint = snapshot.runtimeCheckpoint ?? {};
|
|
1028
|
+
const checkpointStatus = typeof checkpoint.status === "string" ? checkpoint.status : null;
|
|
1029
|
+
const status = snapshot.status ?? checkpointStatus ?? "unknown";
|
|
1030
|
+
const state = checkpointStatus === "done" || checkpointStatus === "completed" || status === "done" || status === "completed" ? "done" : checkpointStatus === "failed" || checkpointStatus === "error" || status === "failed" || status === "error" ? "failed" : status === "running" || status === "pending" || status === "queued" ? "active" : "unknown";
|
|
1031
|
+
const tools = (Array.isArray(snapshot.toolsUsed) ? asNonEmptyStringArray(snapshot.toolsUsed) : asNonEmptyStringArray(checkpoint.lastToolNames)).slice(0, 8);
|
|
1032
|
+
const artifactFiles = snapshot.artifacts && typeof snapshot.artifacts === "object" ? snapshot.artifacts.changedFiles ?? snapshot.artifacts.writtenFiles ?? snapshot.artifacts.files : void 0;
|
|
1033
|
+
const files = (Array.isArray(snapshot.writtenFiles) && snapshot.writtenFiles.length ? asNonEmptyStringArray(snapshot.writtenFiles) : asNonEmptyStringArray(artifactFiles)).slice(0, 8);
|
|
1034
|
+
const subjectRefs = Array.isArray(snapshot.subjectRefs) ? snapshot.subjectRefs.map((ref) => {
|
|
1035
|
+
const kind = asTrimmedString(ref?.kind);
|
|
1036
|
+
const id = asTrimmedString(ref?.id);
|
|
1037
|
+
if (!kind || !id) return "";
|
|
1038
|
+
const rolePart = asOptionalTrimmedString(ref?.role);
|
|
1039
|
+
const role = rolePart ? `#${rolePart}` : "";
|
|
1040
|
+
return `${kind}:${id}${role}`;
|
|
1041
|
+
}).filter(Boolean).slice(0, 8) : [];
|
|
1042
|
+
const runtimeContext = asRecordOrEmpty(snapshot.runtimeContext);
|
|
1043
|
+
const runtimeFieldLines = [
|
|
1044
|
+
["triggerType", snapshot.triggerType],
|
|
1045
|
+
["executionMode", snapshot.executionMode],
|
|
1046
|
+
["threadKind", snapshot.threadKind],
|
|
1047
|
+
["presentationIntent", snapshot.presentationIntent],
|
|
1048
|
+
["parentThreadId", snapshot.parentThreadId],
|
|
1049
|
+
["rootThreadId", snapshot.rootThreadId],
|
|
1050
|
+
["runtimeEntrypoint", runtimeContext.entrypoint ?? checkpoint.runtimeContext?.entrypoint],
|
|
1051
|
+
["parentWakeStatus", snapshot.parentWake?.terminalStatus],
|
|
1052
|
+
["parentWakeAt", snapshot.parentWake?.terminalNotifiedAt]
|
|
1053
|
+
].map(([label, value]) => {
|
|
1054
|
+
const normalized = typeof value === "number" ? String(value) : normalizeNonEmptyString(value);
|
|
1055
|
+
return normalized ? `${label}: ${normalized}` : "";
|
|
1056
|
+
}).filter(Boolean);
|
|
1057
|
+
const toolErrors = asNonEmptyStringArray(snapshot.toolErrors).slice(0, 8);
|
|
1058
|
+
const checkpointUpdatedAt = typeof checkpoint.updatedAt === "number" || typeof checkpoint.updatedAt === "string" ? Date.parse(String(checkpoint.updatedAt)) : NaN;
|
|
1059
|
+
const activeHealth = state !== "active" ? "" : !snapshot.runtimeCheckpoint ? "no-checkpoint" : !Number.isFinite(checkpointUpdatedAt) ? "checkpoint-without-updatedAt" : Date.now() - checkpointUpdatedAt > 5 * 6e4 ? "stale-running" : "running";
|
|
1060
|
+
const errorMessage = compact(checkpoint.errorMessage ?? snapshot.errorMessage);
|
|
1061
|
+
const lines = [
|
|
1062
|
+
`dialog: ${snapshot.dialogId}`,
|
|
1063
|
+
...snapshot.base ? [`base: ${snapshot.base}`] : [],
|
|
1064
|
+
...snapshot.title ? [`title: ${snapshot.title}`] : [],
|
|
1065
|
+
...snapshot.parentDialogId ? [`parentDialogId: ${snapshot.parentDialogId}`] : [],
|
|
1066
|
+
...snapshot.rootDialogId ? [`rootDialogId: ${snapshot.rootDialogId}`] : [],
|
|
1067
|
+
...runtimeFieldLines,
|
|
1068
|
+
`status: ${status}`,
|
|
1069
|
+
...checkpointStatus ? [`checkpoint: ${checkpointStatus}`] : [],
|
|
1070
|
+
`state: ${state}`,
|
|
1071
|
+
...activeHealth ? [`activeHealth: ${activeHealth}`] : [],
|
|
1072
|
+
...typeof snapshot.durationMs === "number" ? [`durationMs: ${snapshot.durationMs}`] : [],
|
|
1073
|
+
...snapshot.updatedAt ? [`updatedAt: ${snapshot.updatedAt}`] : [],
|
|
1074
|
+
...snapshot.finishedAt ? [`finishedAt: ${snapshot.finishedAt}`] : [],
|
|
1075
|
+
...compact(checkpoint.lastUserInput) ? [`lastUserInput: ${compact(checkpoint.lastUserInput)}`] : [],
|
|
1076
|
+
...compact(checkpoint.lastAssistantText) ? [`lastAssistantText: ${compact(checkpoint.lastAssistantText)}`] : [],
|
|
1077
|
+
...errorMessage ? [`error: ${errorMessage}`] : [],
|
|
1078
|
+
...tools.length ? [`tools: ${tools.join(", ")}`] : [],
|
|
1079
|
+
...files.length ? [`files: ${files.join(", ")}`] : [],
|
|
1080
|
+
...subjectRefs.length ? [`subjects: ${subjectRefs.join(", ")}`] : [],
|
|
1081
|
+
...toolErrors.length ? [`toolErrors: ${toolErrors.join(", ")}`] : [],
|
|
1082
|
+
"",
|
|
1083
|
+
"next:",
|
|
1084
|
+
`- read full dialog: nolo dialog read ${snapshot.dialogId} 120`
|
|
1085
|
+
];
|
|
1086
|
+
if (state === "active") lines.push(`- poll again: nolo dialog status ${snapshot.dialogId}`);
|
|
1087
|
+
else if (state === "failed") lines.push(`- inspect failure and rerun/continue with: nolo agent run <agent> --continue ${snapshot.dialogId} --msg "..."`);
|
|
1088
|
+
else if (state === "done") lines.push("- use artifacts/files above for review, handoff, or alpha integration.");
|
|
1089
|
+
else lines.push("- inspect meta/messages before deciding whether to continue or rerun.");
|
|
1090
|
+
return `${lines.join("\n")}
|
|
1091
|
+
`;
|
|
1092
|
+
}
|
|
1093
|
+
async function readSpaceDialogRecords(args) {
|
|
1094
|
+
const { spaceId, spaceKey } = buildSpaceLookup(args.spaceInput);
|
|
1095
|
+
const spaceRead = await readLiveDbRecordAfterTombstoneMerge({
|
|
1096
|
+
authToken: args.authToken,
|
|
1097
|
+
dbKey: spaceKey,
|
|
1098
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
1099
|
+
fetchImpl: args.fetchImpl,
|
|
1100
|
+
label: "space record",
|
|
1101
|
+
serverUrls: args.serverUrls
|
|
1102
|
+
});
|
|
1103
|
+
const contentKeys = [...getSpaceContentKeys(spaceRead.record)].filter((key) => key.startsWith(`dialog-${args.userId}-`));
|
|
1104
|
+
const contentKeySet = new Set(contentKeys);
|
|
1105
|
+
const dialogResult = await listUserRecordsFromServers({
|
|
1106
|
+
authToken: args.authToken,
|
|
1107
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
1108
|
+
fetchImpl: args.fetchImpl,
|
|
1109
|
+
label: "dialog query",
|
|
1110
|
+
serverUrls: args.serverUrls,
|
|
1111
|
+
type: "dialog",
|
|
1112
|
+
userId: args.userId
|
|
1113
|
+
});
|
|
1114
|
+
const records = dialogResult.records.filter(
|
|
1115
|
+
(record) => typeof record?.dbKey === "string" && contentKeySet.has(record.dbKey)
|
|
1116
|
+
);
|
|
1117
|
+
const failures = [...spaceRead.failures, ...dialogResult.failures];
|
|
1118
|
+
return { spaceId, records, failures };
|
|
1119
|
+
}
|
|
1120
|
+
var MAX_ATTEMPT_LINES = 5;
|
|
1121
|
+
function isCleanDialogNotFoundFailure(error) {
|
|
1122
|
+
if (typeof error !== "object" || error === null) return false;
|
|
1123
|
+
const attempts = Array.isArray(error.attempts) ? error.attempts : [];
|
|
1124
|
+
const hasHttp = attempts.length > 0;
|
|
1125
|
+
if (hasHttp) {
|
|
1126
|
+
const allHttp404 = attempts.every((attempt) => attempt.status === 404);
|
|
1127
|
+
if (!allHttp404) return false;
|
|
1128
|
+
}
|
|
1129
|
+
const localFailure = error.local;
|
|
1130
|
+
if (localFailure) {
|
|
1131
|
+
if (localFailure.attempts && localFailure.attempts.length > 0) {
|
|
1132
|
+
const allLocalClean = localFailure.attempts.every(
|
|
1133
|
+
(a) => a.status === "miss" || a.status === "missing" || a.status === "locked"
|
|
1134
|
+
);
|
|
1135
|
+
if (!allLocalClean) return false;
|
|
1136
|
+
} else {
|
|
1137
|
+
return false;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
return hasHttp || Boolean(localFailure);
|
|
1141
|
+
}
|
|
1142
|
+
function formatDialogReadFailure(error, fallbackDialogId) {
|
|
1143
|
+
const targetId = typeof error === "object" && error !== null && typeof error.dialogId === "string" && error.dialogId || fallbackDialogId;
|
|
1144
|
+
if (isCleanDialogNotFoundFailure(error)) {
|
|
1145
|
+
logger.debug(
|
|
1146
|
+
{ error, dialogId: targetId },
|
|
1147
|
+
"dialog not found across all local and HTTP candidates"
|
|
1148
|
+
);
|
|
1149
|
+
return targetId ? `[nolo] dialog ${targetId} not found` : `[nolo] dialog not found`;
|
|
1150
|
+
}
|
|
1151
|
+
const message = toErrorMessage(error);
|
|
1152
|
+
const attempts = typeof error === "object" && error !== null && Array.isArray(error.attempts) ? error.attempts : [];
|
|
1153
|
+
const lines = [`[nolo] dialog read failed: ${message}`];
|
|
1154
|
+
const localFailure = typeof error === "object" && error !== null && "local" in error ? error.local : void 0;
|
|
1155
|
+
if (localFailure) {
|
|
1156
|
+
lines.push(" local:");
|
|
1157
|
+
lines.push(` - attempted: yes`);
|
|
1158
|
+
lines.push(` - reason: ${localFailure.reason}`);
|
|
1159
|
+
if (localFailure.attempts?.length) {
|
|
1160
|
+
lines.push(
|
|
1161
|
+
" - databases tried\uFF08\u56DE\u9000\u94FE: <cwd>/data/leveldb \u2192 NOLO_HOME/data/leveldb\uFF09:"
|
|
1162
|
+
);
|
|
1163
|
+
for (const attempt of localFailure.attempts) {
|
|
1164
|
+
lines.push(` - ${attempt.path} \u2014 ${attempt.status}: ${attempt.reason}`);
|
|
1165
|
+
}
|
|
1166
|
+
} else {
|
|
1167
|
+
lines.push(` - database: ${localFailure.path}`);
|
|
1168
|
+
}
|
|
1169
|
+
if (localFailure.locked) {
|
|
1170
|
+
lines.push(
|
|
1171
|
+
" - \u672C\u5730\u5E93\u6B63\u88AB\u67D0\u4E2A\u8FDB\u7A0B\u6301\u6709 LevelDB \u6392\u4ED6\u9501\uFF08\u5E38\u5E38\u5C31\u662F\u5F53\u524D TUI \u5BBF\u4E3B\u8FDB\u7A0B\u81EA\u5DF1\uFF0C\u672C\u5730\u53EA\u8BFB\u5728\u6301\u9501\u671F\u95F4\u4E0D\u53EF\u7528\uFF09\uFF1B\u8BF7\u901A\u8FC7 server \u7AEF\u70B9\u8BFB\u53D6\u8BE5 dialog\u3002"
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
if (attempts.length > 0) {
|
|
1176
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1177
|
+
const deduped = [];
|
|
1178
|
+
for (const attempt of attempts) {
|
|
1179
|
+
const key = `${attempt.base}|${attempt.status ?? ""}|${attempt.message ?? ""}`;
|
|
1180
|
+
if (seen.has(key)) continue;
|
|
1181
|
+
seen.add(key);
|
|
1182
|
+
deduped.push(attempt);
|
|
1183
|
+
if (deduped.length >= MAX_ATTEMPT_LINES) break;
|
|
1184
|
+
}
|
|
1185
|
+
lines.push(" attempts:");
|
|
1186
|
+
for (const attempt of deduped) {
|
|
1187
|
+
const status = attempt.status != null ? ` HTTP ${attempt.status}` : "";
|
|
1188
|
+
const detail = attempt.message ? ` \u2014 ${attempt.message}` : "";
|
|
1189
|
+
lines.push(` - ${attempt.base}${status}${detail}`);
|
|
1190
|
+
}
|
|
1191
|
+
if (attempts.length > deduped.length) {
|
|
1192
|
+
lines.push(` \u2026 and ${attempts.length - deduped.length} more`);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
const allNotFound = attempts.length > 0 && attempts.every((attempt) => attempt.status === 404);
|
|
1196
|
+
const hasConnRefusedOrTimeout = attempts.some((attempt) => {
|
|
1197
|
+
const text = `${attempt.status ?? ""} ${attempt.message ?? ""}`.toLowerCase();
|
|
1198
|
+
return text.includes("connection refused") || text.includes("econnrefused") || text.includes("enotfound") || text.includes("getaddrinfo") || text.includes("timeout") || text.includes("timed out") || text.includes("fetch failed") || // bun fetch 对连接失败的真实文案(无 refused/timeout 关键词)。
|
|
1199
|
+
text.includes("unable to connect");
|
|
1200
|
+
});
|
|
1201
|
+
if (allNotFound) {
|
|
1202
|
+
const triedPaths = localFailure?.paths?.length ? `\uFF08\u5DF2\u5C1D\u8BD5\uFF1A${localFailure.paths.join(" \u2192 ")}\uFF09` : "";
|
|
1203
|
+
lines.push(
|
|
1204
|
+
" next-step: \u76EE\u6807 dialog \u53EF\u80FD\u4E0D\u5728\u8FD9\u4E9B server \u4E0A\uFF1B\u672C\u5730 run \u7684\u5BF9\u8BDD\u6309 <cwd>/data/leveldb \u2192 NOLO_HOME/data/leveldb\uFF08\u9ED8\u8BA4 ~/.nolo\uFF09\u56DE\u9000\u67E5\u627E" + triedPaths + "\uFF0C\u8BF7\u7528\u5B58\u50A8\u65B9\u5BF9\u5E94\u73AF\u5883/\u670D\u52A1\u8BFB\u53D6\uFF0C\u8FD0\u884C\u4E2D run \u53EF\u7528 `nolo agent status <runId|pid>` \u6216 dialog \u7684 status \u6A21\u5F0F\u67E5\u6458\u8981\u3002"
|
|
1205
|
+
);
|
|
1206
|
+
} else if (hasConnRefusedOrTimeout) {
|
|
1207
|
+
lines.push(
|
|
1208
|
+
" next-step: \u5B58\u5728\u8FDE\u63A5\u62D2\u7EDD/\u8D85\u65F6\uFF0C\u8BF7\u68C0\u67E5 NOLO_SERVER/--server \u6307\u5411\u7684 server \u662F\u5426\u53EF\u8FBE\u3001\u662F\u5426\u5728\u8FD0\u884C\u3002"
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
return lines.join("\n");
|
|
1212
|
+
}
|
|
1213
|
+
async function runDialogReadCommand(args, deps = {}) {
|
|
1214
|
+
const env = deps.env ?? process.env;
|
|
1215
|
+
const output = deps.output ?? process.stdout;
|
|
1216
|
+
if (isHelpArg(args[0])) {
|
|
1217
|
+
printReadUsage(output);
|
|
1218
|
+
return 0;
|
|
1219
|
+
}
|
|
1220
|
+
const authToken = resolveAuthToken(args, env);
|
|
1221
|
+
if (!authToken) {
|
|
1222
|
+
output.write("[nolo] dialog read requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
|
|
1223
|
+
return 1;
|
|
1224
|
+
}
|
|
1225
|
+
let target;
|
|
1226
|
+
try {
|
|
1227
|
+
target = resolveDialogReadTarget(args, env, authToken);
|
|
1228
|
+
} catch (error) {
|
|
1229
|
+
output.write(`${formatDialogReadFailure(error)}
|
|
1230
|
+
`);
|
|
1231
|
+
return 1;
|
|
1232
|
+
}
|
|
1233
|
+
if (!target) {
|
|
1234
|
+
printReadUsage(output);
|
|
1235
|
+
return 1;
|
|
1236
|
+
}
|
|
1237
|
+
try {
|
|
1238
|
+
const limit = readDialogLimitArg(args, 0);
|
|
1239
|
+
const read = await readDialogSnapshot({
|
|
1240
|
+
authToken,
|
|
1241
|
+
base: target.base,
|
|
1242
|
+
dialogId: target.dialogId,
|
|
1243
|
+
dialogKey: target.dialogKey,
|
|
1244
|
+
fetchImpl: deps.fetchImpl ?? fetch,
|
|
1245
|
+
limit,
|
|
1246
|
+
readLocalDialog: deps.readLocalDialog,
|
|
1247
|
+
readLocalDialogAtDbPath: deps.readLocalDialogAtDbPath
|
|
1248
|
+
});
|
|
1249
|
+
const orderedMsgs = Array.isArray(read.msgs) ? [...read.msgs].reverse() : read.msgs;
|
|
1250
|
+
const lastAssistantMessage = Array.isArray(orderedMsgs) ? [...orderedMsgs].reverse().find((message) => message?.role === "assistant" || message?.authorRole === "assistant") : null;
|
|
1251
|
+
const toolMessages = Array.isArray(orderedMsgs) ? orderedMsgs.filter((message) => message?.role === "tool" || message?.authorRole === "tool") : [];
|
|
1252
|
+
const toolNamesFromMessages = toolMessages.map((message) => toToolName(message?.toolName ?? message?.name)).filter(Boolean);
|
|
1253
|
+
const toolsUsed = Array.isArray(read.meta?.toolsUsed) && read.meta.toolsUsed.length > 0 ? read.meta.toolsUsed.map((tool) => toToolName(tool)).filter(Boolean) : uniq(toolNamesFromMessages);
|
|
1254
|
+
const writtenFiles = Array.isArray(read.meta?.writtenFiles) && read.meta.writtenFiles.length > 0 ? uniq(asTrimmedNonEmptyStringArray(read.meta.writtenFiles)) : deriveWrittenFiles(toolMessages);
|
|
1255
|
+
const toolErrors = Array.isArray(read.meta?.toolErrors) && read.meta.toolErrors.length > 0 ? uniq(asTrimmedNonEmptyStringArray(read.meta.toolErrors)) : deriveToolErrors(toolMessages);
|
|
1256
|
+
output.write(JSON.stringify({
|
|
1257
|
+
source: read.source,
|
|
1258
|
+
base: read.resolvedBase,
|
|
1259
|
+
triedBases: read.candidateBases,
|
|
1260
|
+
httpAttempts: read.attempts,
|
|
1261
|
+
spaceId: target.spaceId,
|
|
1262
|
+
dialogId: target.dialogId,
|
|
1263
|
+
dialogKey: target.dialogKey,
|
|
1264
|
+
userId: target.userId,
|
|
1265
|
+
title: read.meta?.title ?? null,
|
|
1266
|
+
cybots: Array.isArray(read.meta?.cybots) ? read.meta.cybots : [],
|
|
1267
|
+
category: read.meta?.category ?? null,
|
|
1268
|
+
inheritedFromDialogKey: read.meta?.inheritedFromDialogKey ?? null,
|
|
1269
|
+
inheritedFromDialogTitle: read.meta?.inheritedFromDialogTitle ?? null,
|
|
1270
|
+
parentDialogId: read.meta?.parentDialogId ?? null,
|
|
1271
|
+
rootDialogId: read.meta?.rootDialogId ?? null,
|
|
1272
|
+
triggerType: read.meta?.triggerType ?? null,
|
|
1273
|
+
executionMode: read.meta?.executionMode ?? null,
|
|
1274
|
+
threadKind: read.meta?.threadKind ?? null,
|
|
1275
|
+
presentationIntent: read.meta?.presentationIntent ?? null,
|
|
1276
|
+
parentThreadId: read.meta?.parentThreadId ?? null,
|
|
1277
|
+
rootThreadId: read.meta?.rootThreadId ?? null,
|
|
1278
|
+
runtimeBinding: read.meta?.runtimeBinding ?? null,
|
|
1279
|
+
runtimeContext: read.meta?.runtimeContext ?? null,
|
|
1280
|
+
parentWake: read.meta?.parentWake ?? null,
|
|
1281
|
+
subjectRefs: Array.isArray(read.meta?.subjectRefs) ? read.meta.subjectRefs : [],
|
|
1282
|
+
status: read.meta?.status,
|
|
1283
|
+
errorMessage: read.meta?.errorMessage ?? null,
|
|
1284
|
+
runtimeCheckpoint: read.meta?.runtimeCheckpoint ?? null,
|
|
1285
|
+
durationMs: read.meta?.durationMs,
|
|
1286
|
+
finishedAt: read.meta?.finishedAt,
|
|
1287
|
+
createdAt: read.meta?.createdAt,
|
|
1288
|
+
updatedAt: read.meta?.updatedAt,
|
|
1289
|
+
artifacts: read.meta?.artifacts ?? null,
|
|
1290
|
+
writtenFiles,
|
|
1291
|
+
toolsUsed,
|
|
1292
|
+
toolErrors,
|
|
1293
|
+
toolSummary: {
|
|
1294
|
+
metaToolsCount: toolsUsed.length,
|
|
1295
|
+
metaToolsUnique: [...new Set(toolsUsed)],
|
|
1296
|
+
toolMessageCount: toolMessages.length,
|
|
1297
|
+
toolNamesFromMessages: [...new Set(toolNamesFromMessages)],
|
|
1298
|
+
toolMessageCountsByName: countByName(toolNamesFromMessages)
|
|
1299
|
+
},
|
|
1300
|
+
agentReply: read.meta?.agentReply ?? null,
|
|
1301
|
+
messagesCount: Array.isArray(orderedMsgs) ? orderedMsgs.length : 0,
|
|
1302
|
+
lastAssistantMessage,
|
|
1303
|
+
messages: orderedMsgs
|
|
1304
|
+
}, null, 2));
|
|
1305
|
+
output.write("\n");
|
|
1306
|
+
return 0;
|
|
1307
|
+
} catch (error) {
|
|
1308
|
+
output.write(`${formatDialogReadFailure(error, target.dialogId)}
|
|
1309
|
+
`);
|
|
1310
|
+
return 1;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
async function runDialogStatusCommand(args, deps = {}) {
|
|
1314
|
+
const env = deps.env ?? process.env;
|
|
1315
|
+
const output = deps.output ?? process.stdout;
|
|
1316
|
+
if (isHelpArg(args[0])) {
|
|
1317
|
+
printStatusUsage(output);
|
|
1318
|
+
return 0;
|
|
1319
|
+
}
|
|
1320
|
+
const authToken = resolveAuthToken(args, env);
|
|
1321
|
+
if (!authToken) {
|
|
1322
|
+
output.write("[nolo] dialog status requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
|
|
1323
|
+
return 1;
|
|
1324
|
+
}
|
|
1325
|
+
let target;
|
|
1326
|
+
try {
|
|
1327
|
+
target = resolveDialogReadTarget(args, env, authToken);
|
|
1328
|
+
} catch (error) {
|
|
1329
|
+
output.write(`[nolo] dialog status failed: ${toErrorMessage(error)}
|
|
1330
|
+
`);
|
|
1331
|
+
return 1;
|
|
1332
|
+
}
|
|
1333
|
+
if (!target) {
|
|
1334
|
+
printStatusUsage(output);
|
|
1335
|
+
return 1;
|
|
1336
|
+
}
|
|
1337
|
+
try {
|
|
1338
|
+
const read = await readDialogSnapshot({
|
|
1339
|
+
authToken,
|
|
1340
|
+
base: target.base,
|
|
1341
|
+
dialogId: target.dialogId,
|
|
1342
|
+
dialogKey: target.dialogKey,
|
|
1343
|
+
fetchImpl: deps.fetchImpl ?? fetch,
|
|
1344
|
+
limit: 1,
|
|
1345
|
+
readLocalDialog: deps.readLocalDialog,
|
|
1346
|
+
readLocalDialogAtDbPath: deps.readLocalDialogAtDbPath
|
|
1347
|
+
});
|
|
1348
|
+
output.write(renderCompactDialogStatus({
|
|
1349
|
+
...read.meta ?? {},
|
|
1350
|
+
dialogId: target.dialogId,
|
|
1351
|
+
dialogKey: target.dialogKey,
|
|
1352
|
+
base: read.resolvedBase
|
|
1353
|
+
}));
|
|
1354
|
+
return 0;
|
|
1355
|
+
} catch (error) {
|
|
1356
|
+
output.write(`[nolo] dialog status failed: ${toErrorMessage(error)}
|
|
1357
|
+
`);
|
|
1358
|
+
return 1;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
async function runDialogQueryCommand(args, deps = {}) {
|
|
1362
|
+
const env = deps.env ?? process.env;
|
|
1363
|
+
const output = deps.output ?? process.stdout;
|
|
1364
|
+
if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
|
|
1365
|
+
printQueryUsage(output);
|
|
1366
|
+
return 0;
|
|
1367
|
+
}
|
|
1368
|
+
const authToken = resolveAuthToken(args, env);
|
|
1369
|
+
if (!authToken) {
|
|
1370
|
+
output.write("[nolo] dialog query requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
|
|
1371
|
+
return 1;
|
|
1372
|
+
}
|
|
1373
|
+
const userId = parseUserIdFromAuthToken(authToken);
|
|
1374
|
+
if (!userId) {
|
|
1375
|
+
output.write("[nolo] dialog query could not read userId from AUTH_TOKEN.\n");
|
|
1376
|
+
return 1;
|
|
1377
|
+
}
|
|
1378
|
+
const rowDbKey = readOption(args, "--row-dbkey") ?? env.TASK_ROW_DBKEY;
|
|
1379
|
+
const subjectKind = readOption(args, "--subject-kind") ?? (rowDbKey ? "table-row" : "");
|
|
1380
|
+
const subjectId = readOption(args, "--subject-id") ?? rowDbKey ?? "";
|
|
1381
|
+
const subjectRole = readOption(args, "--subject-role") ?? (rowDbKey ? "task" : "");
|
|
1382
|
+
if (!subjectKind || !subjectId) {
|
|
1383
|
+
printQueryUsage(output);
|
|
1384
|
+
return 1;
|
|
1385
|
+
}
|
|
1386
|
+
const subjectRef = {
|
|
1387
|
+
kind: normalizeSubjectKind(subjectKind),
|
|
1388
|
+
id: subjectId,
|
|
1389
|
+
...subjectRole ? { role: subjectRole } : {}
|
|
1390
|
+
};
|
|
1391
|
+
const serverUrl = resolveServerUrl(args, env);
|
|
1392
|
+
const resultLimit = resolveResultLimit(args, DEFAULT_DIALOG_QUERY_LIMIT);
|
|
1393
|
+
const limit = resultLimit.unlimited ? UNBOUNDED_DIALOG_CAP : Math.max(1, resultLimit.limit ?? DEFAULT_DIALOG_QUERY_LIMIT);
|
|
1394
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
1395
|
+
const excludeDialogIds = normalizeExcludedDialogIds(readOption(args, "--exclude-dialog"));
|
|
1396
|
+
try {
|
|
1397
|
+
const rawRecords = await queryDialogEvidenceBySubjectRef({
|
|
1398
|
+
authToken,
|
|
1399
|
+
fetchImpl,
|
|
1400
|
+
limit,
|
|
1401
|
+
serverUrl,
|
|
1402
|
+
subjectRef,
|
|
1403
|
+
userId
|
|
1404
|
+
});
|
|
1405
|
+
const records = excludeDialogsById(rawRecords, excludeDialogIds);
|
|
1406
|
+
const strict = verifyDialogSubjectQuery(records, subjectRef, hasFlag(args, "--allow-empty"));
|
|
1407
|
+
const allDialogs = records.filter((record) => dialogMatchesSubjectRef(record, subjectRef)).map((record) => summarizeDialogEvidence(record, subjectRef));
|
|
1408
|
+
const hitLimit = !resultLimit.unlimited && allDialogs.length > limit;
|
|
1409
|
+
const dialogs = hitLimit ? allDialogs.slice(0, limit) : allDialogs;
|
|
1410
|
+
if (hasFlag(args, "--jsonl")) {
|
|
1411
|
+
writeJsonlStream(output, dialogs);
|
|
1412
|
+
} else if (hasFlag(args, "--json")) {
|
|
1413
|
+
writeJsonStream(output, {
|
|
1414
|
+
source: "db.query.subjectRef",
|
|
1415
|
+
readOnly: true,
|
|
1416
|
+
server: serverUrl,
|
|
1417
|
+
userId,
|
|
1418
|
+
...rowDbKey ? { rowDbKey } : {},
|
|
1419
|
+
target: subjectRef,
|
|
1420
|
+
...excludeDialogIds.length ? { excludedDialogIds: excludeDialogIds } : {},
|
|
1421
|
+
strict,
|
|
1422
|
+
total: dialogs.length,
|
|
1423
|
+
...hitLimit ? { limit, truncated: true, nextOffset: dialogs.length } : {},
|
|
1424
|
+
dialogs
|
|
1425
|
+
});
|
|
1426
|
+
} else {
|
|
1427
|
+
output.write(`source: db.query.subjectRef
|
|
1428
|
+
`);
|
|
1429
|
+
output.write(`server: ${serverUrl}
|
|
1430
|
+
`);
|
|
1431
|
+
output.write(`subject: ${subjectRef.kind}:${subjectRef.id}${subjectRef.role ? `#${subjectRef.role}` : ""}
|
|
1432
|
+
`);
|
|
1433
|
+
if (excludeDialogIds.length) output.write(`excluded dialogs: ${excludeDialogIds.join(", ")}
|
|
1434
|
+
`);
|
|
1435
|
+
output.write(`strict: ${strict.ok ? "ok" : strict.reason}
|
|
1436
|
+
`);
|
|
1437
|
+
output.write(`total dialogs: ${dialogs.length}
|
|
1438
|
+
`);
|
|
1439
|
+
if (hitLimit) {
|
|
1440
|
+
output.write(
|
|
1441
|
+
`truncated: showing ${dialogs.length}; use --all or --limit 0 for full dump
|
|
1442
|
+
`
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
for (const dialog of dialogs) {
|
|
1446
|
+
output.write(
|
|
1447
|
+
`
|
|
1448
|
+
${dialog.title ?? "(untitled)"}
|
|
1449
|
+
id=${dialog.dialogId ?? "-"}
|
|
1450
|
+
status=${dialog.status ?? "-"}
|
|
1451
|
+
checkpoint=${dialog.checkpointStatus ?? "-"}
|
|
1452
|
+
artifacts=${dialog.artifactCount}
|
|
1453
|
+
updatedAt=${dialog.updatedAt ?? "-"}
|
|
1454
|
+
dbKey=${dialog.dialogKey ?? "-"}
|
|
1455
|
+
`
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
return strict.ok ? 0 : 1;
|
|
1460
|
+
} catch (error) {
|
|
1461
|
+
output.write(`[nolo] dialog query failed: ${toErrorMessage(error)}
|
|
1462
|
+
`);
|
|
1463
|
+
return 1;
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
async function runDialogListCommand(args, deps = {}) {
|
|
1467
|
+
const env = deps.env ?? process.env;
|
|
1468
|
+
const output = deps.output ?? process.stdout;
|
|
1469
|
+
if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
|
|
1470
|
+
printListUsage(output);
|
|
1471
|
+
return 0;
|
|
1472
|
+
}
|
|
1473
|
+
const authToken = resolveAuthToken(args, env);
|
|
1474
|
+
if (!authToken) {
|
|
1475
|
+
output.write("[nolo] dialog list requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
|
|
1476
|
+
return 1;
|
|
1477
|
+
}
|
|
1478
|
+
const userId = parseUserIdFromAuthToken(authToken);
|
|
1479
|
+
if (!userId) {
|
|
1480
|
+
output.write("[nolo] dialog list could not read userId from AUTH_TOKEN.\n");
|
|
1481
|
+
return 1;
|
|
1482
|
+
}
|
|
1483
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
1484
|
+
const fallbackFetchImpl = deps.fallbackFetchImpl;
|
|
1485
|
+
const serverUrl = resolveServerUrl(args, env);
|
|
1486
|
+
const serverUrls = resolveServerCandidates(args, env, serverUrl);
|
|
1487
|
+
const resultLimit = resolveResultLimit(args, DEFAULT_DIALOG_LIST_LIMIT);
|
|
1488
|
+
const limit = resultLimit.unlimited ? UNBOUNDED_DIALOG_CAP : Math.max(1, resultLimit.limit ?? DEFAULT_DIALOG_LIST_LIMIT);
|
|
1489
|
+
const offset = readOffset(args);
|
|
1490
|
+
const spaceInput = readOption(args, "--space") ?? readOption(args, "--space-id");
|
|
1491
|
+
const includeScheduled = hasFlag(args, "--include-scheduled");
|
|
1492
|
+
try {
|
|
1493
|
+
let source = "user-data";
|
|
1494
|
+
let resolvedSpaceId = null;
|
|
1495
|
+
let queryFailures = [];
|
|
1496
|
+
let records;
|
|
1497
|
+
if (spaceInput) {
|
|
1498
|
+
source = "space";
|
|
1499
|
+
const result = await readSpaceDialogRecords({
|
|
1500
|
+
authToken,
|
|
1501
|
+
fallbackFetchImpl,
|
|
1502
|
+
fetchImpl,
|
|
1503
|
+
serverUrls,
|
|
1504
|
+
spaceInput,
|
|
1505
|
+
userId
|
|
1506
|
+
});
|
|
1507
|
+
resolvedSpaceId = result.spaceId;
|
|
1508
|
+
records = result.records;
|
|
1509
|
+
queryFailures = result.failures;
|
|
1510
|
+
} else {
|
|
1511
|
+
const result = await listUserRecordsFromServers({
|
|
1512
|
+
authToken,
|
|
1513
|
+
fallbackFetchImpl,
|
|
1514
|
+
fetchImpl,
|
|
1515
|
+
label: "dialog query",
|
|
1516
|
+
serverUrls,
|
|
1517
|
+
type: "dialog",
|
|
1518
|
+
userId
|
|
1519
|
+
});
|
|
1520
|
+
records = result.records;
|
|
1521
|
+
queryFailures = result.failures;
|
|
1522
|
+
}
|
|
1523
|
+
const sorted = sortDialogs(
|
|
1524
|
+
records.map((record) => normalizeDialogRecord(record)).filter((dialog) => dialog != null).filter((dialog) => includeScheduled || !isScheduledDialog(dialog))
|
|
1525
|
+
);
|
|
1526
|
+
const window = resultLimit.unlimited ? sorted.slice(offset, offset + UNBOUNDED_DIALOG_CAP) : sorted.slice(offset, offset + limit);
|
|
1527
|
+
const hitLimit = !resultLimit.unlimited && sorted.length > offset + window.length;
|
|
1528
|
+
const dialogs = window;
|
|
1529
|
+
if (hasFlag(args, "--ids-only")) {
|
|
1530
|
+
output.write(`${dialogs.map((dialog) => dialog.id).join("\n")}
|
|
1531
|
+
`);
|
|
1532
|
+
return 0;
|
|
1533
|
+
}
|
|
1534
|
+
if (hasFlag(args, "--jsonl")) {
|
|
1535
|
+
writeJsonlStream(output, dialogs);
|
|
1536
|
+
return 0;
|
|
1537
|
+
}
|
|
1538
|
+
if (hasFlag(args, "--json")) {
|
|
1539
|
+
writeJsonStream(output, {
|
|
1540
|
+
userId,
|
|
1541
|
+
...resolvedSpaceId ? { spaceId: resolvedSpaceId } : {},
|
|
1542
|
+
source,
|
|
1543
|
+
targetServers: serverUrls,
|
|
1544
|
+
...queryFailures.length ? { serverFailures: queryFailures } : {},
|
|
1545
|
+
total: dialogs.length,
|
|
1546
|
+
...hitLimit ? {
|
|
1547
|
+
limit,
|
|
1548
|
+
offset,
|
|
1549
|
+
truncated: true,
|
|
1550
|
+
nextOffset: offset + dialogs.length
|
|
1551
|
+
} : {},
|
|
1552
|
+
dialogs
|
|
1553
|
+
});
|
|
1554
|
+
return 0;
|
|
1555
|
+
}
|
|
1556
|
+
output.write(`userId: ${userId}
|
|
1557
|
+
`);
|
|
1558
|
+
if (resolvedSpaceId) output.write(`spaceId: ${resolvedSpaceId}
|
|
1559
|
+
`);
|
|
1560
|
+
output.write(`source: ${source}
|
|
1561
|
+
`);
|
|
1562
|
+
output.write(`targetServers: ${serverUrls.join(", ")}
|
|
1563
|
+
`);
|
|
1564
|
+
if (queryFailures.length) {
|
|
1565
|
+
output.write(`serverFailures: ${queryFailures.length}
|
|
1566
|
+
`);
|
|
1567
|
+
}
|
|
1568
|
+
output.write(`total dialogs: ${dialogs.length}
|
|
1569
|
+
`);
|
|
1570
|
+
if (hitLimit) {
|
|
1571
|
+
output.write(
|
|
1572
|
+
`truncated: showing ${dialogs.length} (offset ${offset}); use --offset ${offset + dialogs.length} --limit ${limit}, or --all / --limit 0 for full dump
|
|
1573
|
+
`
|
|
1574
|
+
);
|
|
1575
|
+
}
|
|
1576
|
+
for (const dialog of dialogs) {
|
|
1577
|
+
output.write(
|
|
1578
|
+
`
|
|
1579
|
+
${dialog.title}
|
|
1580
|
+
id=${dialog.id}
|
|
1581
|
+
status=${dialog.status ?? "-"}
|
|
1582
|
+
updatedAt=${dialog.updatedAt ?? "-"}
|
|
1583
|
+
dbKey=${dialog.dbKey}
|
|
1584
|
+
`
|
|
1585
|
+
);
|
|
1586
|
+
}
|
|
1587
|
+
return 0;
|
|
1588
|
+
} catch (error) {
|
|
1589
|
+
output.write(
|
|
1590
|
+
`[nolo] dialog list failed: ${toErrorMessage(error)}
|
|
1591
|
+
`
|
|
1592
|
+
);
|
|
1593
|
+
return 1;
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
async function runDialogDeleteCommand(args, deps = {}) {
|
|
1597
|
+
const env = deps.env ?? process.env;
|
|
1598
|
+
const output = deps.output ?? process.stdout;
|
|
1599
|
+
if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
|
|
1600
|
+
printDeleteUsage(output);
|
|
1601
|
+
return 0;
|
|
1602
|
+
}
|
|
1603
|
+
const rawDialogs = readAllPositional(args);
|
|
1604
|
+
if (rawDialogs.length === 0) {
|
|
1605
|
+
printDeleteUsage(output);
|
|
1606
|
+
return 1;
|
|
1607
|
+
}
|
|
1608
|
+
const authToken = resolveAuthToken(args, env);
|
|
1609
|
+
if (!authToken) {
|
|
1610
|
+
output.write("[nolo] dialog delete requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
|
|
1611
|
+
return 1;
|
|
1612
|
+
}
|
|
1613
|
+
const userId = parseUserIdFromAuthToken(authToken);
|
|
1614
|
+
if (!userId) {
|
|
1615
|
+
output.write("[nolo] dialog delete could not read userId from AUTH_TOKEN.\n");
|
|
1616
|
+
return 1;
|
|
1617
|
+
}
|
|
1618
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
1619
|
+
const fallbackFetchImpl = deps.fallbackFetchImpl;
|
|
1620
|
+
const serverUrl = resolveServerUrl(args, env);
|
|
1621
|
+
const serverUrls = resolveDeleteServerCandidates(args, env, serverUrl);
|
|
1622
|
+
const shouldDelete = hasFlag(args, "--yes");
|
|
1623
|
+
const includeReferencedAttachments = hasFlag(args, "--include-referenced-attachments");
|
|
1624
|
+
const includeAttachments = hasFlag(args, "--include-attachments") || includeReferencedAttachments;
|
|
1625
|
+
let hasErrors = false;
|
|
1626
|
+
const payloads = [];
|
|
1627
|
+
for (const rawDialog of rawDialogs) {
|
|
1628
|
+
try {
|
|
1629
|
+
const resolved = resolveDialogInput(rawDialog, userId);
|
|
1630
|
+
const readResult = await readDbRecordFromServers({
|
|
1631
|
+
authToken,
|
|
1632
|
+
dbKey: resolved.dbKey,
|
|
1633
|
+
fallbackFetchImpl,
|
|
1634
|
+
fetchImpl,
|
|
1635
|
+
label: "dialog record",
|
|
1636
|
+
serverUrls
|
|
1637
|
+
});
|
|
1638
|
+
const dialog = normalizeDialogRecord(readResult.record, resolved.dbKey);
|
|
1639
|
+
if (!dialog) {
|
|
1640
|
+
throw new Error(`dialog record is unreadable: ${resolved.dbKey}`);
|
|
1641
|
+
}
|
|
1642
|
+
const attachmentPlan = includeAttachments ? await planDialogAttachmentCleanup({
|
|
1643
|
+
authToken,
|
|
1644
|
+
dialogId: resolved.dialogId,
|
|
1645
|
+
ownerId: resolved.ownerId,
|
|
1646
|
+
includeUserOwnedReferenced: includeReferencedAttachments,
|
|
1647
|
+
fallbackFetchImpl,
|
|
1648
|
+
fetchImpl,
|
|
1649
|
+
serverUrl: readResult.serverUrl,
|
|
1650
|
+
serverUrls
|
|
1651
|
+
}) : null;
|
|
1652
|
+
const attachmentDeleteResults = includeAttachments && shouldDelete && attachmentPlan ? await deleteDialogAttachmentCandidates({
|
|
1653
|
+
authToken,
|
|
1654
|
+
candidates: attachmentPlan.deleteCandidates,
|
|
1655
|
+
fallbackFetchImpl,
|
|
1656
|
+
fetchImpl,
|
|
1657
|
+
serverUrls
|
|
1658
|
+
}) : [];
|
|
1659
|
+
let deleteResults = [];
|
|
1660
|
+
if (shouldDelete) {
|
|
1661
|
+
deleteResults = await deleteDbRecordOnServers({
|
|
1662
|
+
authToken,
|
|
1663
|
+
dbKey: resolved.dbKey,
|
|
1664
|
+
fallbackFetchImpl,
|
|
1665
|
+
fetchImpl,
|
|
1666
|
+
serverUrls
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1669
|
+
const failedDeletes = deleteResults.filter((result) => !result.ok);
|
|
1670
|
+
const failedAttachmentDeletes = attachmentDeleteResults.flatMap(
|
|
1671
|
+
(result) => result.deleteResults.filter((deleteResult) => !deleteResult.ok).map((deleteResult) => ({
|
|
1672
|
+
fileId: result.fileId,
|
|
1673
|
+
fileDbKey: result.fileDbKey,
|
|
1674
|
+
...deleteResult
|
|
1675
|
+
}))
|
|
1676
|
+
);
|
|
1677
|
+
if (failedDeletes.length > 0 || failedAttachmentDeletes.length > 0) {
|
|
1678
|
+
hasErrors = true;
|
|
1679
|
+
}
|
|
1680
|
+
const payload = {
|
|
1681
|
+
dbKey: resolved.dbKey,
|
|
1682
|
+
dialogId: resolved.dialogId,
|
|
1683
|
+
title: dialog.title,
|
|
1684
|
+
readServerUrl: readResult.serverUrl,
|
|
1685
|
+
targetServers: serverUrls,
|
|
1686
|
+
dryRun: !shouldDelete,
|
|
1687
|
+
deleted: shouldDelete,
|
|
1688
|
+
includeAttachments,
|
|
1689
|
+
includeReferencedAttachments,
|
|
1690
|
+
...attachmentPlan ? { attachmentPlan } : {},
|
|
1691
|
+
...readResult.failures.length ? { readFailures: readResult.failures } : {},
|
|
1692
|
+
...shouldDelete ? { deleteResults } : {},
|
|
1693
|
+
...attachmentDeleteResults.length ? { attachmentDeleteResults } : {}
|
|
1694
|
+
};
|
|
1695
|
+
payloads.push(payload);
|
|
1696
|
+
if (!hasFlag(args, "--json")) {
|
|
1697
|
+
output.write(`dialog: ${dialog.title}
|
|
1698
|
+
`);
|
|
1699
|
+
output.write(`dbKey: ${resolved.dbKey}
|
|
1700
|
+
`);
|
|
1701
|
+
output.write(`readServer: ${readResult.serverUrl}
|
|
1702
|
+
`);
|
|
1703
|
+
output.write(`targetServers: ${serverUrls.join(", ")}
|
|
1704
|
+
`);
|
|
1705
|
+
if (attachmentPlan) {
|
|
1706
|
+
output.write(
|
|
1707
|
+
`attachments: ${attachmentPlan.deleteCandidates.length} delete candidates, ${attachmentPlan.retainedCandidates.length} retained, ${attachmentPlan.bytesToDelete} bytes planned
|
|
1708
|
+
`
|
|
1709
|
+
);
|
|
1710
|
+
for (const candidate of attachmentPlan.retainedCandidates) {
|
|
1711
|
+
output.write(`retained attachment: ${candidate.fileId} (${candidate.reason})
|
|
1712
|
+
`);
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
output.write(
|
|
1716
|
+
shouldDelete ? failedDeletes.length > 0 ? `Deleted dialog on ${deleteResults.length - failedDeletes.length}/${deleteResults.length} servers. Failed: ${failedDeletes.map((result) => `${result.serverUrl} (${result.error})`).join(", ")}
|
|
1717
|
+
` : "Deleted dialog on all target servers. Server-side delete cascades dialog messages.\n" : includeAttachments ? "Dry-run only. Add --yes to delete this dialog, its messages, and delete-candidate attachments.\n" : "Dry-run only. Add --yes to delete this dialog and its messages.\n"
|
|
1718
|
+
);
|
|
1719
|
+
if (failedAttachmentDeletes.length > 0) {
|
|
1720
|
+
output.write(
|
|
1721
|
+
`Attachment delete failures: ${failedAttachmentDeletes.map((result) => `${result.fileDbKey}@${result.serverUrl} (${result.error})`).join(", ")}
|
|
1722
|
+
`
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
if (rawDialogs.length > 1) {
|
|
1726
|
+
output.write("\n");
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
} catch (error) {
|
|
1730
|
+
hasErrors = true;
|
|
1731
|
+
const errorMsg = toErrorMessage(error);
|
|
1732
|
+
if (hasFlag(args, "--json")) {
|
|
1733
|
+
payloads.push({
|
|
1734
|
+
rawDialog,
|
|
1735
|
+
error: errorMsg
|
|
1736
|
+
});
|
|
1737
|
+
} else {
|
|
1738
|
+
output.write(`[nolo] dialog delete failed for ${rawDialog}: ${errorMsg}
|
|
1739
|
+
`);
|
|
1740
|
+
if (rawDialogs.length > 1) {
|
|
1741
|
+
output.write("\n");
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
if (hasFlag(args, "--json")) {
|
|
1747
|
+
const finalPayload = rawDialogs.length === 1 ? payloads[0] : payloads;
|
|
1748
|
+
output.write(JSON.stringify(finalPayload, null, 2));
|
|
1749
|
+
output.write("\n");
|
|
1750
|
+
}
|
|
1751
|
+
return hasErrors ? 1 : 0;
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
export {
|
|
1755
|
+
normalizeDialogRecord,
|
|
1756
|
+
sortDialogs,
|
|
1757
|
+
isScheduledDialog,
|
|
1758
|
+
isLevelDbOpenUnavailableError,
|
|
1759
|
+
readDialogSnapshot,
|
|
1760
|
+
isCleanDialogNotFoundFailure,
|
|
1761
|
+
runDialogReadCommand,
|
|
1762
|
+
runDialogStatusCommand,
|
|
1763
|
+
runDialogQueryCommand,
|
|
1764
|
+
runDialogListCommand,
|
|
1765
|
+
runDialogDeleteCommand
|
|
1766
|
+
};
|