@openviking/openclaw-plugin 2026.5.26 → 2026.6.17-dev.1
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/INSTALL-AGENT.md +355 -355
- package/INSTALL-ZH.md +405 -345
- package/INSTALL.md +366 -362
- package/README.md +364 -338
- package/adapters/http-transport.ts +3 -0
- package/adapters/resource-packager.ts +141 -0
- package/auto-recall.ts +518 -257
- package/client.ts +963 -939
- package/commands/setup.ts +1169 -1002
- package/config/feature-gates.json +10 -0
- package/config.ts +819 -476
- package/context-engine.ts +408 -1737
- package/dist/adapters/http-transport.js +1 -0
- package/dist/adapters/resource-packager.js +114 -0
- package/dist/auto-recall.js +228 -32
- package/dist/client.js +125 -161
- package/dist/commands/setup.js +170 -32
- package/dist/config.js +285 -64
- package/dist/context-engine.js +64 -1179
- package/dist/index.js +209 -1470
- package/dist/memory-ranking.js +35 -12
- package/dist/plugin/command-registration.js +5 -0
- package/dist/plugin/openviking-archive-tools.js +210 -0
- package/dist/plugin/openviking-bypass-runtime.js +8 -0
- package/dist/plugin/openviking-client-runtime.js +28 -0
- package/dist/plugin/openviking-command-args.js +146 -0
- package/dist/plugin/openviking-command-definitions.js +206 -0
- package/dist/plugin/openviking-context-engine-ref.js +11 -0
- package/dist/plugin/openviking-context-engine-registration.js +23 -0
- package/dist/plugin/openviking-feature-gates.js +250 -0
- package/dist/plugin/openviking-import-runtime.js +50 -0
- package/dist/plugin/openviking-import-tools.js +86 -0
- package/dist/plugin/openviking-lifecycle-hooks.js +33 -0
- package/dist/plugin/openviking-memory-recall-tools.js +233 -0
- package/dist/plugin/openviking-memory-tools.js +199 -0
- package/dist/plugin/openviking-query-config-command.js +203 -0
- package/dist/plugin/openviking-query-runtime.js +367 -0
- package/dist/plugin/openviking-query-tools.js +91 -0
- package/dist/plugin/openviking-recall-trace-runtime.js +387 -0
- package/dist/plugin/openviking-recall-trace-tools.js +38 -0
- package/dist/plugin/openviking-runtime-state.js +26 -0
- package/dist/plugin/openviking-runtime-utils.js +62 -0
- package/dist/plugin/openviking-services.js +20 -0
- package/dist/plugin/openviking-session-routing-runtime.js +61 -0
- package/dist/plugin/openviking-tool-result-tools.js +268 -0
- package/dist/plugin/recall-trace-routes.js +77 -0
- package/dist/plugin/tool-registration.js +23 -0
- package/dist/process-manager.js +5 -17
- package/dist/query-config.js +336 -0
- package/dist/recall-trace.js +309 -0
- package/dist/registries/recall-resource-types.js +58 -0
- package/dist/routing/identity-routing.js +152 -0
- package/dist/routing/memory-uri.js +7 -0
- package/dist/services/context-lifecycle-service.js +847 -0
- package/dist/services/context-message-adapter.js +340 -0
- package/dist/session-transcript-repair.js +0 -1
- package/dist/text-utils.js +14 -31
- package/dist/token-estimator.js +51 -0
- package/index.ts +408 -1985
- package/install-manifest.json +57 -47
- package/memory-ranking.ts +303 -267
- package/openclaw.plugin.json +497 -335
- package/package.json +86 -76
- package/plugin/command-registration.ts +34 -0
- package/plugin/openviking-archive-tools.ts +301 -0
- package/plugin/openviking-bypass-runtime.ts +26 -0
- package/plugin/openviking-client-runtime.ts +74 -0
- package/plugin/openviking-command-args.ts +187 -0
- package/plugin/openviking-command-definitions.ts +294 -0
- package/plugin/openviking-context-engine-ref.ts +16 -0
- package/plugin/openviking-context-engine-registration.ts +97 -0
- package/plugin/openviking-feature-gates.ts +335 -0
- package/plugin/openviking-import-runtime.ts +105 -0
- package/plugin/openviking-import-tools.ts +133 -0
- package/plugin/openviking-lifecycle-hooks.ts +65 -0
- package/plugin/openviking-memory-recall-tools.ts +368 -0
- package/plugin/openviking-memory-tools.ts +299 -0
- package/plugin/openviking-query-config-command.ts +230 -0
- package/plugin/openviking-query-formatters.ts +108 -0
- package/plugin/openviking-query-runtime.ts +476 -0
- package/plugin/openviking-query-tools.ts +161 -0
- package/plugin/openviking-recall-trace-runtime.ts +482 -0
- package/plugin/openviking-recall-trace-tools.ts +99 -0
- package/plugin/openviking-runtime-state.ts +52 -0
- package/plugin/openviking-runtime-utils.ts +67 -0
- package/plugin/openviking-services.ts +49 -0
- package/plugin/openviking-session-routing-runtime.ts +105 -0
- package/plugin/openviking-tool-result-tools.ts +397 -0
- package/plugin/recall-trace-routes.ts +133 -0
- package/plugin/tool-registration.ts +51 -0
- package/process-manager.ts +41 -45
- package/query-config.ts +400 -0
- package/recall-trace.ts +442 -0
- package/registries/openviking-tools.ts +84 -0
- package/registries/recall-resource-types.ts +74 -0
- package/routing/identity-routing.ts +210 -0
- package/routing/memory-uri.ts +8 -0
- package/runtime-utils.ts +5 -5
- package/services/context-lifecycle-service.ts +1209 -0
- package/services/context-message-adapter.ts +375 -0
- package/services/setup/config-writer.ts +125 -0
- package/services/setup/exit-utils.ts +5 -0
- package/services/setup/package-metadata.ts +56 -0
- package/services/setup/probe-service.ts +143 -0
- package/services/setup/setup-flow.ts +306 -0
- package/services/setup/version-compatibility.ts +41 -0
- package/session-transcript-repair.ts +528 -530
- package/skills/install-openviking-memory/SKILL.md +752 -665
- package/skills/openviking-context-database/SKILL.md +286 -0
- package/text-utils.ts +488 -507
- package/token-estimator.ts +65 -0
- package/tool-call-id.ts +331 -331
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import type { RuntimeQueryParams } from "../query-config.js";
|
|
2
|
+
import type { CommandResult, PluginCommandContext } from "./command-registration.js";
|
|
3
|
+
|
|
4
|
+
type RuntimeScope = "claw" | "session";
|
|
5
|
+
|
|
6
|
+
type ParsedFlagArgs = {
|
|
7
|
+
positionals: string[];
|
|
8
|
+
flags: Map<string, string | boolean>;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type OpenVikingQueryConfigCommandDeps<TSession, TQueryConfigContext, TEffectiveConfig> = {
|
|
12
|
+
resolvePluginSessionRouting: (ctx?: PluginCommandContext) => TSession;
|
|
13
|
+
toQueryConfigContext: (session: TSession) => TQueryConfigContext;
|
|
14
|
+
queryConfigStore: {
|
|
15
|
+
getEffective: (ctx: TQueryConfigContext) => Promise<TEffectiveConfig>;
|
|
16
|
+
set: (scope: RuntimeScope, ctx: TQueryConfigContext, params: RuntimeQueryParams) => Promise<unknown>;
|
|
17
|
+
unset: (scope: RuntimeScope, ctx: TQueryConfigContext, fields: string[]) => Promise<unknown>;
|
|
18
|
+
reset: (scope: RuntimeScope, ctx: TQueryConfigContext) => Promise<unknown>;
|
|
19
|
+
};
|
|
20
|
+
normalizeRuntimeQueryParams: (patch: RuntimeQueryParams & Record<string, unknown>) => {
|
|
21
|
+
params: RuntimeQueryParams;
|
|
22
|
+
warnings: string[];
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function tokenizeCommandArgs(args: string): string[] {
|
|
27
|
+
const tokens: string[] = [];
|
|
28
|
+
let current = "";
|
|
29
|
+
let quote: "'" | '"' | null = null;
|
|
30
|
+
let escaping = false;
|
|
31
|
+
|
|
32
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
33
|
+
const ch = args[i]!;
|
|
34
|
+
const next = args[i + 1];
|
|
35
|
+
if (escaping) {
|
|
36
|
+
current += ch;
|
|
37
|
+
escaping = false;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (ch === "\\") {
|
|
41
|
+
const shouldEscape =
|
|
42
|
+
quote === '"'
|
|
43
|
+
? next === '"' || next === "\\"
|
|
44
|
+
: !quote && Boolean(next && (/\s/.test(next) || next === '"' || next === "'"));
|
|
45
|
+
if (shouldEscape) {
|
|
46
|
+
escaping = true;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
current += ch;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if ((ch === '"' || ch === "'") && (!quote || quote === ch)) {
|
|
53
|
+
quote = quote ? null : ch;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (!quote && /\s/.test(ch)) {
|
|
57
|
+
if (current) {
|
|
58
|
+
tokens.push(current);
|
|
59
|
+
current = "";
|
|
60
|
+
}
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
current += ch;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (escaping) {
|
|
67
|
+
current += "\\";
|
|
68
|
+
}
|
|
69
|
+
if (quote) {
|
|
70
|
+
throw new Error("Unterminated quoted argument");
|
|
71
|
+
}
|
|
72
|
+
if (current) {
|
|
73
|
+
tokens.push(current);
|
|
74
|
+
}
|
|
75
|
+
return tokens;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function parseFlagArgs(args: string): ParsedFlagArgs {
|
|
79
|
+
const tokens = tokenizeCommandArgs(args);
|
|
80
|
+
const positionals: string[] = [];
|
|
81
|
+
const flags = new Map<string, string | boolean>();
|
|
82
|
+
|
|
83
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
84
|
+
const token = tokens[i]!;
|
|
85
|
+
if (!token.startsWith("--")) {
|
|
86
|
+
positionals.push(token);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const raw = token.slice(2);
|
|
90
|
+
if (!raw) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const eqIndex = raw.indexOf("=");
|
|
94
|
+
if (eqIndex >= 0) {
|
|
95
|
+
flags.set(raw.slice(0, eqIndex), raw.slice(eqIndex + 1));
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const next = tokens[i + 1];
|
|
99
|
+
if (next && !next.startsWith("--")) {
|
|
100
|
+
flags.set(raw, next);
|
|
101
|
+
i += 1;
|
|
102
|
+
} else {
|
|
103
|
+
flags.set(raw, true);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { positionals, flags };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function getStringFlag(flags: Map<string, string | boolean>, name: string): string | undefined {
|
|
111
|
+
const value = flags.get(name);
|
|
112
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getNumberFlag(flags: Map<string, string | boolean>, name: string): number | undefined {
|
|
116
|
+
const raw = getStringFlag(flags, name);
|
|
117
|
+
if (!raw) {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
const value = Number(raw);
|
|
121
|
+
if (!Number.isFinite(value)) {
|
|
122
|
+
throw new Error(`--${name} must be a number`);
|
|
123
|
+
}
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getOptionalBoolFlag(flags: Map<string, string | boolean>, name: string): boolean | undefined {
|
|
128
|
+
const value = flags.get(name);
|
|
129
|
+
if (value === undefined) return undefined;
|
|
130
|
+
if (value === true) return true;
|
|
131
|
+
if (value === false) return false;
|
|
132
|
+
const normalized = value.trim().toLowerCase();
|
|
133
|
+
if (["1", "true", "yes", "on"].includes(normalized)) return true;
|
|
134
|
+
if (["0", "false", "no", "off"].includes(normalized)) return false;
|
|
135
|
+
throw new Error(`--${name} must be a boolean`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function parseNumberMapFlag(flags: Map<string, string | boolean>, name: string): Record<string, number> | undefined {
|
|
139
|
+
const raw = getStringFlag(flags, name);
|
|
140
|
+
if (!raw) return undefined;
|
|
141
|
+
const result: Record<string, number> = {};
|
|
142
|
+
for (const item of raw.split(",")) {
|
|
143
|
+
const [key, rawValue, ...rest] = item.split("=");
|
|
144
|
+
const trimmedKey = key?.trim();
|
|
145
|
+
const trimmedValue = rawValue?.trim();
|
|
146
|
+
if (!trimmedKey || !trimmedValue || rest.length > 0) {
|
|
147
|
+
throw new Error(`--${name} must use key=value pairs separated by commas`);
|
|
148
|
+
}
|
|
149
|
+
const value = Number(trimmedValue);
|
|
150
|
+
if (!Number.isFinite(value)) {
|
|
151
|
+
throw new Error(`--${name} value for ${trimmedKey} must be a number`);
|
|
152
|
+
}
|
|
153
|
+
result[trimmedKey] = value;
|
|
154
|
+
}
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function parseQueryConfigPatch(flags: Map<string, string | boolean>): RuntimeQueryParams {
|
|
159
|
+
const patch: RuntimeQueryParams = {};
|
|
160
|
+
const numberFields: Array<[string, keyof RuntimeQueryParams]> = [
|
|
161
|
+
["recallLimit", "recallLimit"],
|
|
162
|
+
["candidateLimit", "candidateLimit"],
|
|
163
|
+
["candidateMultiplier", "candidateMultiplier"],
|
|
164
|
+
["scoreThreshold", "scoreThreshold"],
|
|
165
|
+
["maxInjectedChars", "maxInjectedChars"],
|
|
166
|
+
["ovSearchLimit", "ovSearchLimit"],
|
|
167
|
+
];
|
|
168
|
+
for (const [flag, field] of numberFields) {
|
|
169
|
+
if (flags.has(flag)) {
|
|
170
|
+
(patch as Record<string, unknown>)[field] = getNumberFlag(flags, flag);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const resourceTypes = getStringFlag(flags, "resourceTypes");
|
|
174
|
+
if (resourceTypes) patch.resourceTypes = resourceTypes;
|
|
175
|
+
const targetUri = getStringFlag(flags, "targetUri");
|
|
176
|
+
if (targetUri) patch.targetUri = targetUri;
|
|
177
|
+
const recallPreferAbstract = getOptionalBoolFlag(flags, "recallPreferAbstract");
|
|
178
|
+
if (recallPreferAbstract !== undefined) patch.recallPreferAbstract = recallPreferAbstract;
|
|
179
|
+
|
|
180
|
+
const rankingWeights = parseNumberMapFlag(flags, "weight") ?? parseNumberMapFlag(flags, "rankingWeights");
|
|
181
|
+
if (rankingWeights) patch.rankingWeights = rankingWeights;
|
|
182
|
+
const categoryWeights = parseNumberMapFlag(flags, "categoryWeight") ?? parseNumberMapFlag(flags, "categoryWeights");
|
|
183
|
+
if (categoryWeights) patch.categoryWeights = categoryWeights;
|
|
184
|
+
const resourceTypeWeights = parseNumberMapFlag(flags, "resourceTypeWeight") ?? parseNumberMapFlag(flags, "resourceTypeWeights");
|
|
185
|
+
if (resourceTypeWeights) patch.resourceTypeWeights = resourceTypeWeights as RuntimeQueryParams["resourceTypeWeights"];
|
|
186
|
+
return patch;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function createOpenVikingQueryConfigCommandHandler<TSession, TQueryConfigContext, TEffectiveConfig>(
|
|
190
|
+
deps: OpenVikingQueryConfigCommandDeps<TSession, TQueryConfigContext, TEffectiveConfig>,
|
|
191
|
+
): (ctx: PluginCommandContext) => Promise<CommandResult> {
|
|
192
|
+
return async (ctx: PluginCommandContext): Promise<CommandResult> => {
|
|
193
|
+
const parsed = parseFlagArgs(ctx.args ?? "");
|
|
194
|
+
const action = parsed.positionals[0] ?? "get";
|
|
195
|
+
const session = deps.resolvePluginSessionRouting(ctx);
|
|
196
|
+
const scope: RuntimeScope = getStringFlag(parsed.flags, "scope") === "claw" ? "claw" : "session";
|
|
197
|
+
const queryCtx = deps.toQueryConfigContext(session);
|
|
198
|
+
|
|
199
|
+
if (action === "get") {
|
|
200
|
+
const effective = await deps.queryConfigStore.getEffective(queryCtx);
|
|
201
|
+
return { text: JSON.stringify({ scope, effective }, null, 2), details: { scope, effective } };
|
|
202
|
+
}
|
|
203
|
+
if (action === "set") {
|
|
204
|
+
const patch = parseQueryConfigPatch(parsed.flags);
|
|
205
|
+
const { params, warnings } = deps.normalizeRuntimeQueryParams(patch as RuntimeQueryParams & Record<string, unknown>);
|
|
206
|
+
if (Object.keys(params).length === 0) {
|
|
207
|
+
throw new Error("No query config parameters provided for /ov-query-config set");
|
|
208
|
+
}
|
|
209
|
+
await deps.queryConfigStore.set(scope, queryCtx, params);
|
|
210
|
+
const effective = await deps.queryConfigStore.getEffective(queryCtx);
|
|
211
|
+
return {
|
|
212
|
+
text: `Updated OpenViking query config (${scope}).${warnings.length ? ` Warnings: ${warnings.join("; ")}` : ""}`,
|
|
213
|
+
details: { scope, params, warnings, effective },
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
if (action === "unset") {
|
|
217
|
+
const fields = parsed.positionals.slice(1);
|
|
218
|
+
if (fields.length === 0) throw new Error("Usage: /ov-query-config unset <field...> [--scope session|claw]");
|
|
219
|
+
await deps.queryConfigStore.unset(scope, queryCtx, fields);
|
|
220
|
+
const effective = await deps.queryConfigStore.getEffective(queryCtx);
|
|
221
|
+
return { text: `Unset OpenViking query config fields (${scope}): ${fields.join(", ")}`, details: { scope, fields, effective } };
|
|
222
|
+
}
|
|
223
|
+
if (action === "reset") {
|
|
224
|
+
await deps.queryConfigStore.reset(scope, queryCtx);
|
|
225
|
+
const effective = await deps.queryConfigStore.getEffective(queryCtx);
|
|
226
|
+
return { text: `Reset OpenViking query config (${scope}).`, details: { scope, effective } };
|
|
227
|
+
}
|
|
228
|
+
throw new Error("Usage: /ov-query-config get|set|unset|reset [--scope session|claw] [--recallLimit N] [--candidateLimit N] [--scoreThreshold N] [--resourceTypes user,agent]");
|
|
229
|
+
};
|
|
230
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { FindResult } from "../client.js";
|
|
2
|
+
|
|
3
|
+
export type OVMultiReadTextResult = {
|
|
4
|
+
uri: string;
|
|
5
|
+
content: string;
|
|
6
|
+
success: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function formatOVSearchRows(result: FindResult): string[] {
|
|
10
|
+
const truncateSummary = (value: string, maxChars = 220): string => {
|
|
11
|
+
const collapsed = value.replace(/\s+/g, " ").trim();
|
|
12
|
+
if (collapsed.length <= maxChars) {
|
|
13
|
+
return collapsed;
|
|
14
|
+
}
|
|
15
|
+
return `${collapsed.slice(0, maxChars - 3)}...`;
|
|
16
|
+
};
|
|
17
|
+
const items = [
|
|
18
|
+
...(result.memories ?? []).map((item) => ({ contextType: "memory", item })),
|
|
19
|
+
...(result.resources ?? []).map((item) => ({ contextType: "resource", item })),
|
|
20
|
+
...(result.skills ?? []).map((item) => ({ contextType: "skill", item })),
|
|
21
|
+
];
|
|
22
|
+
if (items.length === 0) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
const numberHeader = "no";
|
|
26
|
+
const numberWidth = Math.max(numberHeader.length, String(items.length).length);
|
|
27
|
+
const typeWidth = Math.max("type".length, ...items.map(({ contextType }) => contextType.length));
|
|
28
|
+
const uriWidth = Math.max("uri".length, ...items.map(({ item }) => item.uri.length));
|
|
29
|
+
const levelWidth = Math.max("level".length, ...items.map(({ item }) => String(item.level ?? "").length));
|
|
30
|
+
const scoreWidth = Math.max(
|
|
31
|
+
"score".length,
|
|
32
|
+
...items.map(({ item }) => (typeof item.score === "number" ? item.score.toFixed(2).length : 0)),
|
|
33
|
+
);
|
|
34
|
+
return [
|
|
35
|
+
`${numberHeader.padEnd(numberWidth)} ${"type".padEnd(typeWidth)} ${"uri".padEnd(uriWidth)} ${"level".padEnd(levelWidth)} ${"score".padEnd(scoreWidth)} abstract`,
|
|
36
|
+
...items.map(({ contextType, item }, index) => {
|
|
37
|
+
const score = typeof item.score === "number" ? item.score.toFixed(2) : "";
|
|
38
|
+
const summary = truncateSummary(item.abstract || item.overview || "(no summary)");
|
|
39
|
+
return `${String(index + 1).padEnd(numberWidth)} ${contextType.padEnd(typeWidth)} ${item.uri.padEnd(uriWidth)} ${String(item.level ?? "").padEnd(levelWidth)} ${score.padEnd(scoreWidth)} ${summary}`;
|
|
40
|
+
}),
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function formatOVSearchText(query: string, uri: string | undefined, result: FindResult): string {
|
|
45
|
+
if ((result.total ?? 0) <= 0) {
|
|
46
|
+
const scope = uri ? ` under ${uri}` : "";
|
|
47
|
+
return `No OpenViking resource or skill results found for "${query}"${scope}.`;
|
|
48
|
+
}
|
|
49
|
+
const scope = uri ? ` under ${uri}` : "";
|
|
50
|
+
const lines = [
|
|
51
|
+
`Found ${result.total ?? 0} OpenViking results for "${query}"${scope}`,
|
|
52
|
+
"Tip: search results are ranked snippets. Use ov_read on exact hit URIs before answering precise questions. Use ov_list on a hit's parent URI to inspect sibling chunks or overview files before answering procedural or multi-step questions.",
|
|
53
|
+
"",
|
|
54
|
+
...formatOVSearchRows(result),
|
|
55
|
+
].filter((line, index, all) => line || (all[index - 1] && all[index + 1]));
|
|
56
|
+
return lines.join("\n");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function formatOVListEntry(entry: unknown): string {
|
|
60
|
+
if (typeof entry === "string") {
|
|
61
|
+
return entry;
|
|
62
|
+
}
|
|
63
|
+
if (!entry || typeof entry !== "object") {
|
|
64
|
+
return String(entry);
|
|
65
|
+
}
|
|
66
|
+
const item = entry as Record<string, unknown>;
|
|
67
|
+
const uri = typeof item.uri === "string" ? item.uri : "";
|
|
68
|
+
const name = typeof item.name === "string" ? item.name : "";
|
|
69
|
+
const isDir = item.isDir === true || item.type === "directory";
|
|
70
|
+
const marker = isDir ? "[dir]" : "[file]";
|
|
71
|
+
const summary =
|
|
72
|
+
typeof item.abstract === "string" && item.abstract.trim()
|
|
73
|
+
? item.abstract.trim().replace(/\s+/g, " ")
|
|
74
|
+
: typeof item.overview === "string" && item.overview.trim()
|
|
75
|
+
? item.overview.trim().replace(/\s+/g, " ")
|
|
76
|
+
: "";
|
|
77
|
+
const label = uri || name || JSON.stringify(item);
|
|
78
|
+
return summary ? `${marker} ${label} - ${summary}` : `${marker} ${label}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function formatOVListText(uri: string, entries: unknown[]): string {
|
|
82
|
+
if (entries.length === 0) {
|
|
83
|
+
return `No OpenViking entries found under ${uri}.`;
|
|
84
|
+
}
|
|
85
|
+
return [
|
|
86
|
+
`Listed ${entries.length} OpenViking entr${entries.length === 1 ? "y" : "ies"} under ${uri}`,
|
|
87
|
+
"",
|
|
88
|
+
...entries.map((entry) => formatOVListEntry(entry)),
|
|
89
|
+
].join("\n");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function formatOVReadText(uri: string, content: string): string {
|
|
93
|
+
const body = content || "(empty OpenViking content)";
|
|
94
|
+
return [`--- START OF ${uri} ---`, body, `--- END OF ${uri} ---`].join("\n");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function formatOVMultiReadText(results: OVMultiReadTextResult[]): string {
|
|
98
|
+
return [
|
|
99
|
+
`Multi-read results for ${results.length} OpenViking resource${results.length === 1 ? "" : "s"}:`,
|
|
100
|
+
"",
|
|
101
|
+
...results.flatMap((result) => [
|
|
102
|
+
`--- START OF ${result.uri} ---`,
|
|
103
|
+
result.success ? (result.content || "(empty OpenViking content)") : `ERROR: ${result.content}`,
|
|
104
|
+
`--- END OF ${result.uri} ---`,
|
|
105
|
+
"",
|
|
106
|
+
]),
|
|
107
|
+
].join("\n").trimEnd();
|
|
108
|
+
}
|