@eclipse-docks/extension-ai-system 0.7.141 → 0.8.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/README.md +2 -2
- package/dist/api.js +1 -1
- package/dist/{cognitive-extension-CG_d39K5.js → cognitive-extension-7_ZMUCGD.js} +358 -92
- package/dist/cognitive-extension-7_ZMUCGD.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/runtime/abort.d.ts +5 -0
- package/dist/runtime/abort.d.ts.map +1 -0
- package/dist/runtime/attachment-paths.d.ts +17 -0
- package/dist/runtime/attachment-paths.d.ts.map +1 -0
- package/dist/runtime/attachments.d.ts +5 -5
- package/dist/runtime/attachments.d.ts.map +1 -1
- package/dist/runtime/cognitive-runtime.d.ts +7 -3
- package/dist/runtime/cognitive-runtime.d.ts.map +1 -1
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/models/openai-compatible.d.ts.map +1 -1
- package/dist/runtime/session-workspace.d.ts +20 -0
- package/dist/runtime/session-workspace.d.ts.map +1 -0
- package/dist/runtime/specialists/index.d.ts.map +1 -1
- package/dist/runtime/specialists/loop.d.ts.map +1 -1
- package/dist/runtime/tools/command-adapter.d.ts.map +1 -1
- package/dist/runtime/tools/index.d.ts +0 -1
- package/dist/runtime/tools/index.d.ts.map +1 -1
- package/dist/runtime/tools/workspace-adapter.d.ts +1 -1
- package/dist/runtime/tools/workspace-adapter.d.ts.map +1 -1
- package/dist/runtime/tools/workspace-path-qualify.d.ts +22 -0
- package/dist/runtime/tools/workspace-path-qualify.d.ts.map +1 -0
- package/dist/runtime/tools/workspace-path.d.ts +17 -0
- package/dist/runtime/tools/workspace-path.d.ts.map +1 -0
- package/dist/{runtime-DfNGzEjU.js → runtime-By5PP2BC.js} +696 -384
- package/dist/runtime-By5PP2BC.js.map +1 -0
- package/dist/view/busy-status.d.ts +12 -0
- package/dist/view/busy-status.d.ts.map +1 -0
- package/dist/view/cognitive-settings-editor.d.ts +0 -1
- package/dist/view/cognitive-settings-editor.d.ts.map +1 -1
- package/dist/view/cognitive-view.d.ts +21 -0
- package/dist/view/cognitive-view.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/cognitive-extension-CG_d39K5.js.map +0 -1
- package/dist/runtime/tools/attachment-adapter.d.ts +0 -6
- package/dist/runtime/tools/attachment-adapter.d.ts.map +0 -1
- package/dist/runtime-DfNGzEjU.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Directory, File, commandRegistry, persistenceService, workspaceService } from "@eclipse-docks/core";
|
|
1
|
+
import { Directory, File, IN_MEMORY_ROOT_NAME, commandRegistry, filebrowserDialog, getInMemoryRoot, persistenceService, setInMemoryMountProjection, workspaceService } from "@eclipse-docks/core";
|
|
2
2
|
import { Models } from "@opencode-ai/models";
|
|
3
3
|
//#region src/runtime/events.ts
|
|
4
4
|
/** Tiny in-process bus so runtime stays UI-agnostic. */
|
|
@@ -548,6 +548,17 @@ function normalizeProvidersSettings(raw) {
|
|
|
548
548
|
};
|
|
549
549
|
}
|
|
550
550
|
//#endregion
|
|
551
|
+
//#region src/runtime/abort.ts
|
|
552
|
+
/** True when an AbortSignal has already been aborted. */
|
|
553
|
+
function isAborted(signal) {
|
|
554
|
+
return Boolean(signal?.aborted);
|
|
555
|
+
}
|
|
556
|
+
/** True for DOMException / Error abort failures from fetch and friends. */
|
|
557
|
+
function isAbortError(err) {
|
|
558
|
+
if (!err || typeof err !== "object") return false;
|
|
559
|
+
return err.name === "AbortError";
|
|
560
|
+
}
|
|
561
|
+
//#endregion
|
|
551
562
|
//#region src/runtime/models/openai-compatible.ts
|
|
552
563
|
function wireContent(content) {
|
|
553
564
|
if (typeof content === "string") return content;
|
|
@@ -665,6 +676,10 @@ var OpenAICompatibleProvider = class {
|
|
|
665
676
|
signal: params.signal
|
|
666
677
|
});
|
|
667
678
|
} catch (err) {
|
|
679
|
+
if (params.signal?.aborted || isAbortError(err)) {
|
|
680
|
+
yield { type: "done" };
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
668
683
|
yield {
|
|
669
684
|
type: "error",
|
|
670
685
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -683,7 +698,21 @@ var OpenAICompatibleProvider = class {
|
|
|
683
698
|
let buffer = "";
|
|
684
699
|
const toolAcc = /* @__PURE__ */ new Map();
|
|
685
700
|
while (true) {
|
|
686
|
-
|
|
701
|
+
let done;
|
|
702
|
+
let value;
|
|
703
|
+
try {
|
|
704
|
+
({done, value} = await reader.read());
|
|
705
|
+
} catch (err) {
|
|
706
|
+
if (params.signal?.aborted || isAbortError(err)) {
|
|
707
|
+
yield { type: "done" };
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
yield {
|
|
711
|
+
type: "error",
|
|
712
|
+
error: err instanceof Error ? err.message : String(err)
|
|
713
|
+
};
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
687
716
|
if (done) break;
|
|
688
717
|
buffer += decoder.decode(value, { stream: true });
|
|
689
718
|
const lines = buffer.split("\n");
|
|
@@ -955,227 +984,40 @@ function isMutatingName(name) {
|
|
|
955
984
|
return /^(write|edit|delete|mkdir|rm|mv|save|apply)/i.test(name) || name.includes("write") || name.includes("delete");
|
|
956
985
|
}
|
|
957
986
|
//#endregion
|
|
958
|
-
//#region src/runtime/
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
}
|
|
975
|
-
}, async (call) => {
|
|
976
|
-
try {
|
|
977
|
-
const path = String(call.arguments.path ?? "");
|
|
978
|
-
const root = await workspaceService.getWorkspace();
|
|
979
|
-
if (!root) return {
|
|
980
|
-
id: call.id,
|
|
981
|
-
name: call.name,
|
|
982
|
-
ok: false,
|
|
983
|
-
output: null,
|
|
984
|
-
error: "No workspace"
|
|
985
|
-
};
|
|
986
|
-
const resource = await root.getResource(path.endsWith("/") ? path : `${path}/`);
|
|
987
|
-
if (!(resource instanceof Directory)) return {
|
|
988
|
-
id: call.id,
|
|
989
|
-
name: call.name,
|
|
990
|
-
ok: false,
|
|
991
|
-
output: null,
|
|
992
|
-
error: "Not a directory"
|
|
993
|
-
};
|
|
994
|
-
const children = await resource.listChildren(true);
|
|
995
|
-
return {
|
|
996
|
-
id: call.id,
|
|
997
|
-
name: call.name,
|
|
998
|
-
ok: true,
|
|
999
|
-
output: children.map((c) => ({
|
|
1000
|
-
name: c.getName(),
|
|
1001
|
-
path: c.getWorkspacePath()
|
|
1002
|
-
}))
|
|
1003
|
-
};
|
|
1004
|
-
} catch (err) {
|
|
1005
|
-
return {
|
|
1006
|
-
id: call.id,
|
|
1007
|
-
name: call.name,
|
|
1008
|
-
ok: false,
|
|
1009
|
-
output: null,
|
|
1010
|
-
error: err instanceof Error ? err.message : String(err)
|
|
1011
|
-
};
|
|
1012
|
-
}
|
|
1013
|
-
});
|
|
1014
|
-
broker.register({
|
|
1015
|
-
name: "workspace_read",
|
|
1016
|
-
description: "Read a workspace file as text",
|
|
1017
|
-
parameters: {
|
|
1018
|
-
type: "object",
|
|
1019
|
-
properties: { path: { type: "string" } },
|
|
1020
|
-
required: ["path"]
|
|
1021
|
-
}
|
|
1022
|
-
}, async (call) => {
|
|
1023
|
-
try {
|
|
1024
|
-
const path = String(call.arguments.path ?? "");
|
|
1025
|
-
const root = await workspaceService.getWorkspace();
|
|
1026
|
-
if (!root) return {
|
|
1027
|
-
id: call.id,
|
|
1028
|
-
name: call.name,
|
|
1029
|
-
ok: false,
|
|
1030
|
-
output: null,
|
|
1031
|
-
error: "No workspace"
|
|
1032
|
-
};
|
|
1033
|
-
const resource = await root.getResource(path);
|
|
1034
|
-
if (!(resource instanceof File)) return {
|
|
1035
|
-
id: call.id,
|
|
1036
|
-
name: call.name,
|
|
1037
|
-
ok: false,
|
|
1038
|
-
output: null,
|
|
1039
|
-
error: "Not a file"
|
|
1040
|
-
};
|
|
1041
|
-
const contents = await resource.getContents();
|
|
1042
|
-
return {
|
|
1043
|
-
id: call.id,
|
|
1044
|
-
name: call.name,
|
|
1045
|
-
ok: true,
|
|
1046
|
-
output: contents
|
|
1047
|
-
};
|
|
1048
|
-
} catch (err) {
|
|
1049
|
-
return {
|
|
1050
|
-
id: call.id,
|
|
1051
|
-
name: call.name,
|
|
1052
|
-
ok: false,
|
|
1053
|
-
output: null,
|
|
1054
|
-
error: err instanceof Error ? err.message : String(err)
|
|
1055
|
-
};
|
|
1056
|
-
}
|
|
1057
|
-
});
|
|
1058
|
-
broker.register({
|
|
1059
|
-
name: "workspace_write",
|
|
1060
|
-
description: "Write text contents to a workspace file (creates parents as needed)",
|
|
1061
|
-
parameters: {
|
|
1062
|
-
type: "object",
|
|
1063
|
-
properties: {
|
|
1064
|
-
path: { type: "string" },
|
|
1065
|
-
contents: { type: "string" }
|
|
1066
|
-
},
|
|
1067
|
-
required: ["path", "contents"]
|
|
1068
|
-
}
|
|
1069
|
-
}, async (call) => {
|
|
1070
|
-
try {
|
|
1071
|
-
const args = asRecord(call.arguments);
|
|
1072
|
-
const path = String(args.path ?? "");
|
|
1073
|
-
const contents = String(args.contents ?? "");
|
|
1074
|
-
const root = await workspaceService.getWorkspace();
|
|
1075
|
-
if (!root) return {
|
|
1076
|
-
id: call.id,
|
|
1077
|
-
name: call.name,
|
|
1078
|
-
ok: false,
|
|
1079
|
-
output: null,
|
|
1080
|
-
error: "No workspace",
|
|
1081
|
-
mutating: true
|
|
1082
|
-
};
|
|
1083
|
-
const resource = await root.getResource(path, { create: true });
|
|
1084
|
-
if (!(resource instanceof File)) return {
|
|
1085
|
-
id: call.id,
|
|
1086
|
-
name: call.name,
|
|
1087
|
-
ok: false,
|
|
1088
|
-
output: null,
|
|
1089
|
-
error: "Not a file",
|
|
1090
|
-
mutating: true
|
|
1091
|
-
};
|
|
1092
|
-
await resource.saveContents(contents);
|
|
1093
|
-
return {
|
|
1094
|
-
id: call.id,
|
|
1095
|
-
name: call.name,
|
|
1096
|
-
ok: true,
|
|
1097
|
-
output: { path },
|
|
1098
|
-
mutating: true
|
|
1099
|
-
};
|
|
1100
|
-
} catch (err) {
|
|
1101
|
-
return {
|
|
1102
|
-
id: call.id,
|
|
1103
|
-
name: call.name,
|
|
1104
|
-
ok: false,
|
|
1105
|
-
output: null,
|
|
1106
|
-
error: err instanceof Error ? err.message : String(err),
|
|
1107
|
-
mutating: true
|
|
1108
|
-
};
|
|
1109
|
-
}
|
|
1110
|
-
});
|
|
987
|
+
//#region src/runtime/attachment-paths.ts
|
|
988
|
+
/** Mirrors core `IN_MEMORY_ROOT_NAME` without importing the filesys graph. */
|
|
989
|
+
var IN_MEMORY_ROOT_NAME$1 = "in-memory";
|
|
990
|
+
/** URI scheme for UI references (`in-memory://<sessionId>/<file>`). */
|
|
991
|
+
var IN_MEMORY_URI_SCHEME = "in-memory://";
|
|
992
|
+
/** Sanitize a single path segment (no slashes). Prefer id-prefixed names for uniqueness. */
|
|
993
|
+
function safeAttachmentFileName(name, id) {
|
|
994
|
+
return `${id}-${(name || "attachment").replace(/[/\\]/g, "_").trim() || "attachment"}`;
|
|
995
|
+
}
|
|
996
|
+
/** Workspace path used by workspace_* tools (no protocol). */
|
|
997
|
+
function attachmentWorkspacePath(sessionId, stored) {
|
|
998
|
+
return `${IN_MEMORY_ROOT_NAME$1}/${sessionId}/${safeAttachmentFileName(stored.name, stored.id)}`;
|
|
999
|
+
}
|
|
1000
|
+
/** UI / catalog URI form (`in-memory://<sessionId>/<file>`). */
|
|
1001
|
+
function attachmentUri(sessionId, stored) {
|
|
1002
|
+
return `${IN_MEMORY_URI_SCHEME}${sessionId}/${safeAttachmentFileName(stored.name, stored.id)}`;
|
|
1111
1003
|
}
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
"
|
|
1120
|
-
"
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
function sanitizeName(id) {
|
|
1124
|
-
return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
1125
|
-
}
|
|
1126
|
-
/** Registers Docks commands as first-class LLM tools (tool name = command id). */
|
|
1127
|
-
function refreshCommandTools(broker, role) {
|
|
1128
|
-
for (const def of broker.listDefinitions("build")) if (!KEEP_TOOLS.has(def.name)) broker.unregister(def.name);
|
|
1129
|
-
const commands = commandRegistry.listCommands(commandRegistry.createExecutionContext());
|
|
1130
|
-
const entries = Object.values(commands);
|
|
1131
|
-
for (const command of entries) {
|
|
1132
|
-
if (!command?.id || SKIP.has(command.id)) continue;
|
|
1133
|
-
if (!command.description) continue;
|
|
1134
|
-
if ((role === "plan" || role === "explore") && /delete|write|save|rm|mv/i.test(command.id)) continue;
|
|
1135
|
-
const toolName = sanitizeName(command.id);
|
|
1136
|
-
if (KEEP_TOOLS.has(toolName)) continue;
|
|
1137
|
-
const properties = {};
|
|
1138
|
-
const required = [];
|
|
1139
|
-
for (const p of command.parameters ?? []) {
|
|
1140
|
-
properties[p.name] = {
|
|
1141
|
-
type: p.type ?? "string",
|
|
1142
|
-
description: p.description,
|
|
1143
|
-
...p.allowedValues ? { enum: p.allowedValues } : {}
|
|
1144
|
-
};
|
|
1145
|
-
if (p.required) required.push(p.name);
|
|
1146
|
-
}
|
|
1147
|
-
broker.register({
|
|
1148
|
-
name: toolName,
|
|
1149
|
-
description: `${command.name}: ${command.description}`,
|
|
1150
|
-
parameters: {
|
|
1151
|
-
type: "object",
|
|
1152
|
-
properties,
|
|
1153
|
-
required
|
|
1154
|
-
}
|
|
1155
|
-
}, async (call, _sessionId) => {
|
|
1156
|
-
try {
|
|
1157
|
-
const result = await commandRegistry.execute(command.id, { params: call.arguments });
|
|
1158
|
-
return {
|
|
1159
|
-
id: call.id,
|
|
1160
|
-
name: call.name,
|
|
1161
|
-
ok: true,
|
|
1162
|
-
output: result
|
|
1163
|
-
};
|
|
1164
|
-
} catch (err) {
|
|
1165
|
-
return {
|
|
1166
|
-
id: call.id,
|
|
1167
|
-
name: call.name,
|
|
1168
|
-
ok: false,
|
|
1169
|
-
output: null,
|
|
1170
|
-
error: err instanceof Error ? err.message : String(err)
|
|
1171
|
-
};
|
|
1172
|
-
}
|
|
1173
|
-
});
|
|
1004
|
+
/**
|
|
1005
|
+
* Map `in-memory://…` references to composite workspace paths.
|
|
1006
|
+
* Real workspace paths are returned unchanged.
|
|
1007
|
+
*/
|
|
1008
|
+
function toWorkspaceToolPath(path) {
|
|
1009
|
+
const trimmed = path.trim();
|
|
1010
|
+
const lower = trimmed.toLowerCase();
|
|
1011
|
+
if (lower === "in-memory:" || lower === "in-memory://" || lower === "in-memory") return IN_MEMORY_ROOT_NAME$1;
|
|
1012
|
+
if (lower.startsWith("in-memory://")) {
|
|
1013
|
+
const rest = trimmed.slice(12).replace(/^\/+/, "");
|
|
1014
|
+
return rest ? `${IN_MEMORY_ROOT_NAME$1}/${rest}` : IN_MEMORY_ROOT_NAME$1;
|
|
1174
1015
|
}
|
|
1016
|
+
return trimmed;
|
|
1175
1017
|
}
|
|
1176
1018
|
//#endregion
|
|
1177
1019
|
//#region src/runtime/attachments.ts
|
|
1178
|
-
/** Soft cap per attached text file when stored / returned
|
|
1020
|
+
/** Soft cap per attached text file when stored / returned via workspace_read. */
|
|
1179
1021
|
var MAX_ATTACHMENT_CHARS = 1e5;
|
|
1180
1022
|
/** Soft cap for visual media attachments (bytes). */
|
|
1181
1023
|
var MAX_IMAGE_BYTES = 4e6;
|
|
@@ -1384,7 +1226,7 @@ function toSessionAttachment(attachment, opts = {}) {
|
|
|
1384
1226
|
messageId: opts.messageId
|
|
1385
1227
|
};
|
|
1386
1228
|
}
|
|
1387
|
-
/** Lightweight history/UI reference — no payload. */
|
|
1229
|
+
/** Lightweight history/UI reference — no payload (path is an in-memory workspace path/URI). */
|
|
1388
1230
|
function toFileRefPart(stored) {
|
|
1389
1231
|
return {
|
|
1390
1232
|
type: "file",
|
|
@@ -1398,14 +1240,15 @@ function toFileRefPart(stored) {
|
|
|
1398
1240
|
}
|
|
1399
1241
|
/** One-line catalog entry published into the user turn for the model. */
|
|
1400
1242
|
function formatAttachmentRefForModel(part) {
|
|
1401
|
-
const
|
|
1243
|
+
const display = part.path || part.name;
|
|
1244
|
+
const toolPath = toWorkspaceToolPath(display);
|
|
1402
1245
|
const kind = part.kind ?? "file";
|
|
1403
1246
|
const bytes = part.byteLength != null ? ` bytes=${part.byteLength}` : "";
|
|
1404
|
-
return `[Attachment published:
|
|
1247
|
+
return `[Attachment published: ${display} kind=${kind}${part.mime ? ` mime=${part.mime}` : ""}${bytes}] → workspace_read path="${toolPath}"`;
|
|
1405
1248
|
}
|
|
1406
1249
|
/**
|
|
1407
1250
|
* Build provider user content from history parts.
|
|
1408
|
-
* File parts are references only — payloads
|
|
1251
|
+
* File parts are references only — payloads live under the in-memory workspace root.
|
|
1409
1252
|
*/
|
|
1410
1253
|
function userContentFromParts(parts) {
|
|
1411
1254
|
const texts = [];
|
|
@@ -1416,71 +1259,10 @@ function userContentFromParts(parts) {
|
|
|
1416
1259
|
}
|
|
1417
1260
|
const body = [...texts, ...refs].join("\n").trim();
|
|
1418
1261
|
if (!refs.length) return body;
|
|
1419
|
-
return [body, "
|
|
1420
|
-
}
|
|
1421
|
-
function attachmentMeta(stored) {
|
|
1422
|
-
return {
|
|
1423
|
-
id: stored.id,
|
|
1424
|
-
name: stored.name,
|
|
1425
|
-
path: stored.path,
|
|
1426
|
-
mime: stored.mime,
|
|
1427
|
-
kind: stored.kind,
|
|
1428
|
-
byteLength: stored.byteLength,
|
|
1429
|
-
truncated: stored.truncated
|
|
1430
|
-
};
|
|
1431
|
-
}
|
|
1432
|
-
function readTextAttachment(stored, opts = {}) {
|
|
1433
|
-
if (stored.kind !== "text" && !(stored.kind === "image" && stored.text)) return {
|
|
1434
|
-
ok: false,
|
|
1435
|
-
error: `${stored.name} is not a text attachment`
|
|
1436
|
-
};
|
|
1437
|
-
const full = stored.text ?? "";
|
|
1438
|
-
const offset = Math.max(0, opts.offset ?? 0);
|
|
1439
|
-
const limit = Math.max(1, opts.limit ?? 4e4);
|
|
1440
|
-
const slice = full.slice(offset, offset + limit);
|
|
1441
|
-
return {
|
|
1442
|
-
ok: true,
|
|
1443
|
-
output: {
|
|
1444
|
-
...attachmentMeta(stored),
|
|
1445
|
-
offset,
|
|
1446
|
-
length: slice.length,
|
|
1447
|
-
totalLength: full.length,
|
|
1448
|
-
truncated: offset + slice.length < full.length,
|
|
1449
|
-
content: slice
|
|
1450
|
-
}
|
|
1451
|
-
};
|
|
1262
|
+
return [body, "Session attachments are files under the in-memory workspace root. List with workspace_list, read with workspace_read. Do not connect storage backends to find them."].filter(Boolean).join("\n");
|
|
1452
1263
|
}
|
|
1453
1264
|
/** Marker field: specialist loop presents image bytes in a follow-up multimodal message. */
|
|
1454
1265
|
var IMAGE_PRESENT_KEY = "_presentImage";
|
|
1455
|
-
function readImageAttachment(stored, opts = {}) {
|
|
1456
|
-
if (stored.kind !== "image") return {
|
|
1457
|
-
ok: false,
|
|
1458
|
-
error: `${stored.name} is not an image attachment`
|
|
1459
|
-
};
|
|
1460
|
-
if (stored.text != null && isSvgAttachment(stored)) return readTextAttachment(stored, opts);
|
|
1461
|
-
if (!stored.dataUrl) return {
|
|
1462
|
-
ok: false,
|
|
1463
|
-
error: `${stored.name} has no image payload`
|
|
1464
|
-
};
|
|
1465
|
-
const present = resolveMediaType({
|
|
1466
|
-
name: stored.name,
|
|
1467
|
-
mime: stored.mime
|
|
1468
|
-
})?.vision !== false;
|
|
1469
|
-
return {
|
|
1470
|
-
ok: true,
|
|
1471
|
-
output: {
|
|
1472
|
-
...attachmentMeta(stored),
|
|
1473
|
-
...present ? {
|
|
1474
|
-
[IMAGE_PRESENT_KEY]: true,
|
|
1475
|
-
dataUrl: stored.dataUrl,
|
|
1476
|
-
note: "Image bytes will be provided to the model in the next message for vision inspection."
|
|
1477
|
-
} : {
|
|
1478
|
-
dataUrlOmitted: true,
|
|
1479
|
-
note: "This media type is stored for preview; vision presentation is skipped. Ask for a raster export if needed."
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
};
|
|
1483
|
-
}
|
|
1484
1266
|
function stripAttachmentToolOutput(output) {
|
|
1485
1267
|
if (!output || typeof output !== "object") return output;
|
|
1486
1268
|
const copy = { ...output };
|
|
@@ -1611,105 +1393,451 @@ async function readPromptAttachmentFromFile(file) {
|
|
|
1611
1393
|
};
|
|
1612
1394
|
}
|
|
1613
1395
|
//#endregion
|
|
1614
|
-
//#region src/runtime/tools/
|
|
1615
|
-
|
|
1616
|
-
|
|
1396
|
+
//#region src/runtime/tools/workspace-path-qualify.ts
|
|
1397
|
+
function isInMemoryToolPath(path) {
|
|
1398
|
+
return path === "in-memory" || path.startsWith(`in-memory/`);
|
|
1399
|
+
}
|
|
1400
|
+
function isWorkspaceRootPath(path) {
|
|
1401
|
+
const trimmed = path.trim();
|
|
1402
|
+
return !trimmed || trimmed === "." || trimmed === "/";
|
|
1403
|
+
}
|
|
1404
|
+
/** First path segment — composite roots are keyed by this name. */
|
|
1405
|
+
function firstPathSegment(path) {
|
|
1406
|
+
return path.split("/").filter(Boolean)[0] ?? "";
|
|
1407
|
+
}
|
|
1408
|
+
function pathHasKnownRoot(path, rootNames) {
|
|
1409
|
+
const first = firstPathSegment(path);
|
|
1410
|
+
if (!first) return false;
|
|
1411
|
+
return (rootNames instanceof Set ? rootNames : new Set(rootNames)).has(first);
|
|
1412
|
+
}
|
|
1413
|
+
function joinUnderRoot(rootPath, relativePath) {
|
|
1414
|
+
const root = rootPath.replace(/\/+$/, "");
|
|
1415
|
+
const relative = relativePath.replace(/^\/+/, "");
|
|
1416
|
+
if (!root) return relative;
|
|
1417
|
+
if (!relative) return root;
|
|
1418
|
+
return `${root}/${relative}`;
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Decide how to qualify a workspace tool path that is missing a connected-root prefix.
|
|
1422
|
+
*/
|
|
1423
|
+
function decideRootQualification(path, durableRoots, mode) {
|
|
1424
|
+
if (isInMemoryToolPath(path) || isWorkspaceRootPath(path)) return { action: "keep" };
|
|
1425
|
+
if (pathHasKnownRoot(path, durableRoots)) return { action: "keep" };
|
|
1426
|
+
if (durableRoots.length === 1) return {
|
|
1427
|
+
action: "prefix",
|
|
1428
|
+
root: durableRoots[0]
|
|
1429
|
+
};
|
|
1430
|
+
if (durableRoots.length === 0) return {
|
|
1431
|
+
action: "error",
|
|
1432
|
+
error: mode === "write" ? "No durable workspace folder connected. Connect a folder before writing, or use an in-memory/… path." : "No workspace folder connected."
|
|
1433
|
+
};
|
|
1434
|
+
if (mode === "write") return { action: "pick" };
|
|
1435
|
+
return {
|
|
1436
|
+
action: "error",
|
|
1437
|
+
error: `Path must start with a workspace root (${durableRoots.join(", ")}). Example: ${durableRoots[0]}/${path}`
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
//#endregion
|
|
1441
|
+
//#region src/runtime/tools/workspace-path.ts
|
|
1442
|
+
/**
|
|
1443
|
+
* Ensure tool paths are under a composite workspace root.
|
|
1444
|
+
* - Already rooted / in-memory / empty → unchanged
|
|
1445
|
+
* - One durable root → auto-prefix
|
|
1446
|
+
* - Several durable roots (writes) → folder picker dialog
|
|
1447
|
+
*/
|
|
1448
|
+
async function qualifyWorkspaceToolPath(rawPath, mode = "write") {
|
|
1449
|
+
const path = toWorkspaceToolPath(String(rawPath ?? ""));
|
|
1450
|
+
if (isInMemoryToolPath(path) || isWorkspaceRootPath(path)) return {
|
|
1451
|
+
ok: true,
|
|
1452
|
+
path
|
|
1453
|
+
};
|
|
1454
|
+
const folders = await workspaceService.getFolders();
|
|
1455
|
+
const allNames = folders.map((f) => f.name);
|
|
1456
|
+
if (pathHasKnownRoot(path, allNames)) return {
|
|
1457
|
+
ok: true,
|
|
1458
|
+
path
|
|
1459
|
+
};
|
|
1460
|
+
const durableRoots = folders.filter((f) => f.type !== "in-memory").map((f) => f.name);
|
|
1461
|
+
const decision = decideRootQualification(path, mode === "write" ? durableRoots : allNames.length ? allNames : durableRoots, mode);
|
|
1462
|
+
if (decision.action === "keep") return {
|
|
1463
|
+
ok: true,
|
|
1464
|
+
path
|
|
1465
|
+
};
|
|
1466
|
+
if (decision.action === "prefix") return {
|
|
1467
|
+
ok: true,
|
|
1468
|
+
path: joinUnderRoot(decision.root, path),
|
|
1469
|
+
resolvedFrom: path
|
|
1470
|
+
};
|
|
1471
|
+
if (decision.action === "error") return {
|
|
1472
|
+
ok: false,
|
|
1473
|
+
error: decision.error
|
|
1474
|
+
};
|
|
1475
|
+
const chosen = await filebrowserDialog("directory");
|
|
1476
|
+
if (!chosen) return {
|
|
1477
|
+
ok: false,
|
|
1478
|
+
error: "Write cancelled: no workspace folder selected."
|
|
1479
|
+
};
|
|
1480
|
+
return {
|
|
1481
|
+
ok: true,
|
|
1482
|
+
path: joinUnderRoot(chosen, path),
|
|
1483
|
+
resolvedFrom: path
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
//#endregion
|
|
1487
|
+
//#region src/runtime/tools/workspace-adapter.ts
|
|
1488
|
+
function asRecord(value) {
|
|
1489
|
+
return value && typeof value === "object" ? value : {};
|
|
1490
|
+
}
|
|
1491
|
+
/** Some File backends (e.g. in-memory) expose mime; the abstract File does not. */
|
|
1492
|
+
function optionalMimeType(resource) {
|
|
1493
|
+
const getMimeType = resource.getMimeType;
|
|
1494
|
+
return typeof getMimeType === "function" ? getMimeType.call(resource) : void 0;
|
|
1495
|
+
}
|
|
1496
|
+
function fileNameFromPath(path) {
|
|
1497
|
+
const parts = path.split("/").filter(Boolean);
|
|
1498
|
+
return parts[parts.length - 1] || path;
|
|
1499
|
+
}
|
|
1500
|
+
/** Resolve a tool path against the shared in-memory root or the mounted workspace. */
|
|
1501
|
+
async function resolveResource(path, options) {
|
|
1502
|
+
if (isInMemoryToolPath(path)) {
|
|
1503
|
+
const root = getInMemoryRoot();
|
|
1504
|
+
const relative = path === IN_MEMORY_ROOT_NAME ? "" : path.slice(`${IN_MEMORY_ROOT_NAME}/`.length);
|
|
1505
|
+
if (!relative) return {
|
|
1506
|
+
resource: root,
|
|
1507
|
+
path: IN_MEMORY_ROOT_NAME
|
|
1508
|
+
};
|
|
1509
|
+
const lookup = options?.directory && !relative.endsWith("/") ? `${relative}/` : relative;
|
|
1510
|
+
return {
|
|
1511
|
+
resource: await root.getResource(lookup, options?.create ? { create: true } : void 0),
|
|
1512
|
+
path
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
const workspace = await workspaceService.getWorkspace();
|
|
1516
|
+
if (!workspace) return {
|
|
1517
|
+
resource: null,
|
|
1518
|
+
path
|
|
1519
|
+
};
|
|
1520
|
+
if (isWorkspaceRootPath(path)) return {
|
|
1521
|
+
resource: workspace,
|
|
1522
|
+
path: ""
|
|
1523
|
+
};
|
|
1524
|
+
const lookup = options?.directory && !path.endsWith("/") ? `${path}/` : path;
|
|
1525
|
+
return {
|
|
1526
|
+
resource: await workspace.getResource(lookup, options?.create ? { create: true } : void 0),
|
|
1527
|
+
path
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
/**
|
|
1531
|
+
* Present image-like workspace files for vision (data URLs or known image extensions).
|
|
1532
|
+
* Returns null when the file should be treated as ordinary text.
|
|
1533
|
+
*/
|
|
1534
|
+
function visionToolOutput(path, contents, mimeHint) {
|
|
1535
|
+
const name = fileNameFromPath(path);
|
|
1536
|
+
const dataUrl = typeof contents === "string" && contents.startsWith("data:image/") ? contents : void 0;
|
|
1537
|
+
const mime = mimeHint || (dataUrl ? dataUrl.slice(5, dataUrl.indexOf(";")) : void 0) || resolveMediaType({
|
|
1538
|
+
name,
|
|
1539
|
+
mime: mimeHint
|
|
1540
|
+
})?.mime;
|
|
1541
|
+
if (isSvgAttachment({
|
|
1542
|
+
name,
|
|
1543
|
+
mime,
|
|
1544
|
+
text: dataUrl ? void 0 : contents
|
|
1545
|
+
})) return null;
|
|
1546
|
+
const media = resolveMediaType({
|
|
1547
|
+
name,
|
|
1548
|
+
mime
|
|
1549
|
+
});
|
|
1550
|
+
if (!dataUrl && media?.mode !== "raster") return null;
|
|
1551
|
+
if (!dataUrl) return null;
|
|
1552
|
+
return {
|
|
1553
|
+
path,
|
|
1554
|
+
name,
|
|
1555
|
+
mime,
|
|
1556
|
+
kind: "image",
|
|
1557
|
+
...media?.vision !== false ? {
|
|
1558
|
+
[IMAGE_PRESENT_KEY]: true,
|
|
1559
|
+
dataUrl,
|
|
1560
|
+
note: "Image bytes will be provided to the model in the next message for vision inspection."
|
|
1561
|
+
} : {
|
|
1562
|
+
dataUrlOmitted: true,
|
|
1563
|
+
note: "This media type is stored for preview; vision presentation is skipped."
|
|
1564
|
+
}
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
/** Registers built-in workspace file tools (paths may use in-memory:// URI form). */
|
|
1568
|
+
function registerWorkspaceTools(broker) {
|
|
1617
1569
|
broker.register({
|
|
1618
|
-
name: "
|
|
1619
|
-
description: "List
|
|
1570
|
+
name: "workspace_list",
|
|
1571
|
+
description: "List children of a workspace directory. Omit path (or use \"\" / \"/\") for connected roots (use those path prefixes for writes). in-memory/… works without mounting.",
|
|
1620
1572
|
parameters: {
|
|
1621
1573
|
type: "object",
|
|
1622
|
-
properties: {
|
|
1574
|
+
properties: { path: {
|
|
1575
|
+
type: "string",
|
|
1576
|
+
description: "Directory path under a connected root (empty = list roots). in-memory://… URIs are accepted."
|
|
1577
|
+
} },
|
|
1578
|
+
required: []
|
|
1579
|
+
}
|
|
1580
|
+
}, async (call) => {
|
|
1581
|
+
try {
|
|
1582
|
+
const path = toWorkspaceToolPath(String(call.arguments.path ?? ""));
|
|
1583
|
+
const { resource } = await resolveResource(path, { directory: true });
|
|
1584
|
+
if (!(resource instanceof Directory)) return {
|
|
1585
|
+
id: call.id,
|
|
1586
|
+
name: call.name,
|
|
1587
|
+
ok: false,
|
|
1588
|
+
output: null,
|
|
1589
|
+
error: isInMemoryToolPath(path) ? "Not a directory" : "No workspace / not a directory"
|
|
1590
|
+
};
|
|
1591
|
+
const children = await resource.listChildren(true);
|
|
1592
|
+
return {
|
|
1593
|
+
id: call.id,
|
|
1594
|
+
name: call.name,
|
|
1595
|
+
ok: true,
|
|
1596
|
+
output: children.map((c) => ({
|
|
1597
|
+
name: c.getName(),
|
|
1598
|
+
path: c.getWorkspacePath(),
|
|
1599
|
+
kind: c instanceof Directory ? "directory" : "file"
|
|
1600
|
+
}))
|
|
1601
|
+
};
|
|
1602
|
+
} catch (err) {
|
|
1603
|
+
return {
|
|
1604
|
+
id: call.id,
|
|
1605
|
+
name: call.name,
|
|
1606
|
+
ok: false,
|
|
1607
|
+
output: null,
|
|
1608
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1609
|
+
};
|
|
1623
1610
|
}
|
|
1624
|
-
}, async (call, sessionId) => {
|
|
1625
|
-
const items = lookup(sessionId).map(attachmentMeta);
|
|
1626
|
-
return {
|
|
1627
|
-
id: call.id,
|
|
1628
|
-
name: call.name,
|
|
1629
|
-
ok: true,
|
|
1630
|
-
output: {
|
|
1631
|
-
count: items.length,
|
|
1632
|
-
attachments: items
|
|
1633
|
-
}
|
|
1634
|
-
};
|
|
1635
1611
|
});
|
|
1636
1612
|
broker.register({
|
|
1637
|
-
name: "
|
|
1638
|
-
description: "
|
|
1613
|
+
name: "workspace_read",
|
|
1614
|
+
description: "Read a workspace file as text. Paths should be root/… from workspace_list; a bare name is auto-rooted when only one folder is connected. Image data-URL files are presented for vision.",
|
|
1639
1615
|
parameters: {
|
|
1640
1616
|
type: "object",
|
|
1641
1617
|
properties: {
|
|
1642
|
-
|
|
1618
|
+
path: {
|
|
1643
1619
|
type: "string",
|
|
1644
|
-
description: "
|
|
1620
|
+
description: "File path under a connected root (e.g. tests/readme.md)."
|
|
1645
1621
|
},
|
|
1646
1622
|
offset: {
|
|
1647
1623
|
type: "number",
|
|
1648
|
-
description: "Text start offset
|
|
1624
|
+
description: "Text start offset"
|
|
1649
1625
|
},
|
|
1650
1626
|
limit: {
|
|
1651
1627
|
type: "number",
|
|
1652
1628
|
description: "Max text characters to return"
|
|
1653
1629
|
}
|
|
1654
1630
|
},
|
|
1655
|
-
required: ["
|
|
1631
|
+
required: ["path"]
|
|
1656
1632
|
}
|
|
1657
|
-
}, async (call
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
name: call.name,
|
|
1662
|
-
ok: false,
|
|
1663
|
-
output: null,
|
|
1664
|
-
error: "id is required"
|
|
1665
|
-
};
|
|
1666
|
-
const stored = lookup(sessionId, id)[0];
|
|
1667
|
-
if (!stored) return {
|
|
1668
|
-
id: call.id,
|
|
1669
|
-
name: call.name,
|
|
1670
|
-
ok: false,
|
|
1671
|
-
output: null,
|
|
1672
|
-
error: `Unknown attachment: ${id}`
|
|
1673
|
-
};
|
|
1674
|
-
if (stored.kind === "image") {
|
|
1675
|
-
const result = readImageAttachment(stored, {
|
|
1676
|
-
offset: typeof call.arguments.offset === "number" ? call.arguments.offset : void 0,
|
|
1677
|
-
limit: typeof call.arguments.limit === "number" ? call.arguments.limit : void 0
|
|
1678
|
-
});
|
|
1679
|
-
if (!result.ok) return {
|
|
1633
|
+
}, async (call) => {
|
|
1634
|
+
try {
|
|
1635
|
+
const qualified = await qualifyWorkspaceToolPath(String(call.arguments.path ?? ""), "read");
|
|
1636
|
+
if (!qualified.ok) return {
|
|
1680
1637
|
id: call.id,
|
|
1681
1638
|
name: call.name,
|
|
1682
1639
|
ok: false,
|
|
1683
1640
|
output: null,
|
|
1684
|
-
error:
|
|
1641
|
+
error: qualified.error
|
|
1685
1642
|
};
|
|
1643
|
+
const path = qualified.path;
|
|
1644
|
+
const { resource } = await resolveResource(path);
|
|
1645
|
+
if (!(resource instanceof File)) return {
|
|
1646
|
+
id: call.id,
|
|
1647
|
+
name: call.name,
|
|
1648
|
+
ok: false,
|
|
1649
|
+
output: null,
|
|
1650
|
+
error: isInMemoryToolPath(path) ? "Not a file" : "No workspace / not a file"
|
|
1651
|
+
};
|
|
1652
|
+
const contents = await resource.getContents();
|
|
1653
|
+
const text = typeof contents === "string" ? contents : String(contents ?? "");
|
|
1654
|
+
const vision = visionToolOutput(path, text, optionalMimeType(resource));
|
|
1655
|
+
if (vision) return {
|
|
1656
|
+
id: call.id,
|
|
1657
|
+
name: call.name,
|
|
1658
|
+
ok: true,
|
|
1659
|
+
output: vision
|
|
1660
|
+
};
|
|
1661
|
+
const offset = typeof call.arguments.offset === "number" ? Math.max(0, call.arguments.offset) : 0;
|
|
1662
|
+
const limit = typeof call.arguments.limit === "number" ? Math.max(1, call.arguments.limit) : void 0;
|
|
1663
|
+
if (offset || limit != null) {
|
|
1664
|
+
const slice = text.slice(offset, limit != null ? offset + limit : void 0);
|
|
1665
|
+
return {
|
|
1666
|
+
id: call.id,
|
|
1667
|
+
name: call.name,
|
|
1668
|
+
ok: true,
|
|
1669
|
+
output: {
|
|
1670
|
+
path,
|
|
1671
|
+
offset,
|
|
1672
|
+
length: slice.length,
|
|
1673
|
+
totalLength: text.length,
|
|
1674
|
+
truncated: offset + slice.length < text.length,
|
|
1675
|
+
content: slice
|
|
1676
|
+
}
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1686
1679
|
return {
|
|
1687
1680
|
id: call.id,
|
|
1688
1681
|
name: call.name,
|
|
1689
1682
|
ok: true,
|
|
1690
|
-
output:
|
|
1683
|
+
output: text
|
|
1684
|
+
};
|
|
1685
|
+
} catch (err) {
|
|
1686
|
+
return {
|
|
1687
|
+
id: call.id,
|
|
1688
|
+
name: call.name,
|
|
1689
|
+
ok: false,
|
|
1690
|
+
output: null,
|
|
1691
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
});
|
|
1695
|
+
broker.register({
|
|
1696
|
+
name: "workspace_write",
|
|
1697
|
+
description: "Write text to a workspace file (creates parents as needed). Prefer root/… paths from workspace_list. A bare filename uses the only durable root, or opens a folder picker when several roots are connected.",
|
|
1698
|
+
parameters: {
|
|
1699
|
+
type: "object",
|
|
1700
|
+
properties: {
|
|
1701
|
+
path: {
|
|
1702
|
+
type: "string",
|
|
1703
|
+
description: "File path under a connected root (e.g. tests/demo.canvas.js)."
|
|
1704
|
+
},
|
|
1705
|
+
contents: { type: "string" }
|
|
1706
|
+
},
|
|
1707
|
+
required: ["path", "contents"]
|
|
1708
|
+
}
|
|
1709
|
+
}, async (call) => {
|
|
1710
|
+
try {
|
|
1711
|
+
const args = asRecord(call.arguments);
|
|
1712
|
+
const qualified = await qualifyWorkspaceToolPath(String(args.path ?? ""), "write");
|
|
1713
|
+
if (!qualified.ok) return {
|
|
1714
|
+
id: call.id,
|
|
1715
|
+
name: call.name,
|
|
1716
|
+
ok: false,
|
|
1717
|
+
output: null,
|
|
1718
|
+
error: qualified.error,
|
|
1719
|
+
mutating: true
|
|
1720
|
+
};
|
|
1721
|
+
const path = qualified.path;
|
|
1722
|
+
const contents = String(args.contents ?? "");
|
|
1723
|
+
const { resource } = await resolveResource(path, { create: true });
|
|
1724
|
+
if (!(resource instanceof File)) return {
|
|
1725
|
+
id: call.id,
|
|
1726
|
+
name: call.name,
|
|
1727
|
+
ok: false,
|
|
1728
|
+
output: null,
|
|
1729
|
+
error: isInMemoryToolPath(path) ? "Not a file" : "No workspace / not a file",
|
|
1730
|
+
mutating: true
|
|
1731
|
+
};
|
|
1732
|
+
await resource.saveContents(contents);
|
|
1733
|
+
return {
|
|
1734
|
+
id: call.id,
|
|
1735
|
+
name: call.name,
|
|
1736
|
+
ok: true,
|
|
1737
|
+
output: {
|
|
1738
|
+
path,
|
|
1739
|
+
...qualified.resolvedFrom ? { resolvedFrom: qualified.resolvedFrom } : {}
|
|
1740
|
+
},
|
|
1741
|
+
mutating: true
|
|
1742
|
+
};
|
|
1743
|
+
} catch (err) {
|
|
1744
|
+
return {
|
|
1745
|
+
id: call.id,
|
|
1746
|
+
name: call.name,
|
|
1747
|
+
ok: false,
|
|
1748
|
+
output: null,
|
|
1749
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1750
|
+
mutating: true
|
|
1691
1751
|
};
|
|
1692
1752
|
}
|
|
1693
|
-
const result = readTextAttachment(stored, {
|
|
1694
|
-
offset: typeof call.arguments.offset === "number" ? call.arguments.offset : void 0,
|
|
1695
|
-
limit: typeof call.arguments.limit === "number" ? call.arguments.limit : void 0
|
|
1696
|
-
});
|
|
1697
|
-
if (!result.ok) return {
|
|
1698
|
-
id: call.id,
|
|
1699
|
-
name: call.name,
|
|
1700
|
-
ok: false,
|
|
1701
|
-
output: null,
|
|
1702
|
-
error: result.error
|
|
1703
|
-
};
|
|
1704
|
-
return {
|
|
1705
|
-
id: call.id,
|
|
1706
|
-
name: call.name,
|
|
1707
|
-
ok: true,
|
|
1708
|
-
output: result.output
|
|
1709
|
-
};
|
|
1710
1753
|
});
|
|
1711
1754
|
}
|
|
1712
1755
|
//#endregion
|
|
1756
|
+
//#region src/runtime/tools/command-adapter.ts
|
|
1757
|
+
/** Commands that must never be exposed as LLM tools (pickers, storage boots, settings). */
|
|
1758
|
+
var SKIP = /* @__PURE__ */ new Set([
|
|
1759
|
+
"open_cognitive_ai_settings",
|
|
1760
|
+
"open_ai_config",
|
|
1761
|
+
"load_workspace",
|
|
1762
|
+
"connect_opfs",
|
|
1763
|
+
"connect_indexeddb",
|
|
1764
|
+
"disconnect_workspace",
|
|
1765
|
+
"disconnect_folder",
|
|
1766
|
+
"open_editor",
|
|
1767
|
+
"list_extensions",
|
|
1768
|
+
"enable_extension",
|
|
1769
|
+
"disable_extension"
|
|
1770
|
+
]);
|
|
1771
|
+
var SKIP_PREFIXES = [
|
|
1772
|
+
"connect_",
|
|
1773
|
+
"disconnect_",
|
|
1774
|
+
"install_",
|
|
1775
|
+
"uninstall_"
|
|
1776
|
+
];
|
|
1777
|
+
/** Built-in tools that must survive command refresh (not command-backed). */
|
|
1778
|
+
var KEEP_TOOLS = /* @__PURE__ */ new Set([
|
|
1779
|
+
"workspace_list",
|
|
1780
|
+
"workspace_read",
|
|
1781
|
+
"workspace_write"
|
|
1782
|
+
]);
|
|
1783
|
+
function sanitizeName(id) {
|
|
1784
|
+
return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
1785
|
+
}
|
|
1786
|
+
function shouldSkipCommand(id) {
|
|
1787
|
+
if (SKIP.has(id)) return true;
|
|
1788
|
+
return SKIP_PREFIXES.some((prefix) => id.startsWith(prefix));
|
|
1789
|
+
}
|
|
1790
|
+
/** Registers Docks commands as first-class LLM tools (tool name = command id). */
|
|
1791
|
+
function refreshCommandTools(broker, role) {
|
|
1792
|
+
for (const def of broker.listDefinitions("build")) if (!KEEP_TOOLS.has(def.name)) broker.unregister(def.name);
|
|
1793
|
+
const commands = commandRegistry.listCommands(commandRegistry.createExecutionContext());
|
|
1794
|
+
const entries = Object.values(commands);
|
|
1795
|
+
for (const command of entries) {
|
|
1796
|
+
if (!command?.id || shouldSkipCommand(command.id)) continue;
|
|
1797
|
+
if (!command.description) continue;
|
|
1798
|
+
if ((role === "plan" || role === "explore") && /delete|write|save|rm|mv/i.test(command.id)) continue;
|
|
1799
|
+
const toolName = sanitizeName(command.id);
|
|
1800
|
+
if (KEEP_TOOLS.has(toolName)) continue;
|
|
1801
|
+
const properties = {};
|
|
1802
|
+
const required = [];
|
|
1803
|
+
for (const p of command.parameters ?? []) {
|
|
1804
|
+
properties[p.name] = {
|
|
1805
|
+
type: p.type ?? "string",
|
|
1806
|
+
description: p.description,
|
|
1807
|
+
...p.allowedValues ? { enum: p.allowedValues } : {}
|
|
1808
|
+
};
|
|
1809
|
+
if (p.required) required.push(p.name);
|
|
1810
|
+
}
|
|
1811
|
+
broker.register({
|
|
1812
|
+
name: toolName,
|
|
1813
|
+
description: `${command.name}: ${command.description}`,
|
|
1814
|
+
parameters: {
|
|
1815
|
+
type: "object",
|
|
1816
|
+
properties,
|
|
1817
|
+
required
|
|
1818
|
+
}
|
|
1819
|
+
}, async (call, _sessionId) => {
|
|
1820
|
+
try {
|
|
1821
|
+
const result = await commandRegistry.execute(command.id, { params: call.arguments });
|
|
1822
|
+
return {
|
|
1823
|
+
id: call.id,
|
|
1824
|
+
name: call.name,
|
|
1825
|
+
ok: true,
|
|
1826
|
+
output: result
|
|
1827
|
+
};
|
|
1828
|
+
} catch (err) {
|
|
1829
|
+
return {
|
|
1830
|
+
id: call.id,
|
|
1831
|
+
name: call.name,
|
|
1832
|
+
ok: false,
|
|
1833
|
+
output: null,
|
|
1834
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
//#endregion
|
|
1713
1841
|
//#region src/runtime/compact/compaction.ts
|
|
1714
1842
|
var COMPACT_SYSTEM = `You compress older chat turns into a durable summary for future assistant turns.
|
|
1715
1843
|
Preserve: user goals, decisions, constraints, important file/paths, tool outcomes that still matter, and open questions.
|
|
@@ -2163,7 +2291,21 @@ async function runSpecialistLoop(input) {
|
|
|
2163
2291
|
};
|
|
2164
2292
|
const messages = historyToProviderMessages(input.systemPrompt, input.history, input.compactionSummary);
|
|
2165
2293
|
let success = true;
|
|
2294
|
+
let cancelled = false;
|
|
2295
|
+
const markCancelled = () => {
|
|
2296
|
+
if (cancelled) return;
|
|
2297
|
+
cancelled = true;
|
|
2298
|
+
success = false;
|
|
2299
|
+
if (!parts.some((p) => p.type === "text")) emit({
|
|
2300
|
+
type: "text",
|
|
2301
|
+
text: "Cancelled."
|
|
2302
|
+
});
|
|
2303
|
+
};
|
|
2166
2304
|
for (let iter = 0; iter < MAX_TOOL_ITERATIONS; iter++) {
|
|
2305
|
+
if (isAborted(input.signal)) {
|
|
2306
|
+
markCancelled();
|
|
2307
|
+
break;
|
|
2308
|
+
}
|
|
2167
2309
|
input.refreshTools?.();
|
|
2168
2310
|
const tools = input.broker.listDefinitions(input.role);
|
|
2169
2311
|
let pendingTools = [];
|
|
@@ -2175,6 +2317,7 @@ async function runSpecialistLoop(input) {
|
|
|
2175
2317
|
providerOptions: input.resolved.providerOptions,
|
|
2176
2318
|
signal: input.signal
|
|
2177
2319
|
})) {
|
|
2320
|
+
if (isAborted(input.signal)) break;
|
|
2178
2321
|
if (chunk.type === "error") {
|
|
2179
2322
|
emit({
|
|
2180
2323
|
type: "text",
|
|
@@ -2203,6 +2346,10 @@ async function runSpecialistLoop(input) {
|
|
|
2203
2346
|
} else pendingTools.push({ ...chunk.toolCall });
|
|
2204
2347
|
}
|
|
2205
2348
|
}
|
|
2349
|
+
if (isAborted(input.signal)) {
|
|
2350
|
+
markCancelled();
|
|
2351
|
+
break;
|
|
2352
|
+
}
|
|
2206
2353
|
if (!pendingTools.length) {
|
|
2207
2354
|
if (assistantText && !parts.some((p) => p.type === "text")) emit({
|
|
2208
2355
|
type: "text",
|
|
@@ -2229,7 +2376,12 @@ async function runSpecialistLoop(input) {
|
|
|
2229
2376
|
})),
|
|
2230
2377
|
signal: input.signal
|
|
2231
2378
|
}) : null;
|
|
2379
|
+
const pendingImages = [];
|
|
2232
2380
|
for (const tool of pendingTools) {
|
|
2381
|
+
if (isAborted(input.signal)) {
|
|
2382
|
+
markCancelled();
|
|
2383
|
+
break;
|
|
2384
|
+
}
|
|
2233
2385
|
let args = {};
|
|
2234
2386
|
try {
|
|
2235
2387
|
args = tool.arguments ? JSON.parse(tool.arguments) : {};
|
|
@@ -2267,26 +2419,30 @@ async function runSpecialistLoop(input) {
|
|
|
2267
2419
|
name: tool.name,
|
|
2268
2420
|
content: JSON.stringify(presentImage ? {
|
|
2269
2421
|
...safeOutput ?? {},
|
|
2270
|
-
note: "Image bytes follow
|
|
2422
|
+
note: "Image bytes follow after all tool results for vision inspection."
|
|
2271
2423
|
} : safeOutput)
|
|
2272
2424
|
});
|
|
2273
|
-
if (presentImage) {
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
role: "user",
|
|
2279
|
-
content: [{
|
|
2280
|
-
type: "text",
|
|
2281
|
-
text: `Attachment image for inspection: id=${id} name="${name}"`
|
|
2282
|
-
}, {
|
|
2283
|
-
type: "image_url",
|
|
2284
|
-
image_url: { url: dataUrl }
|
|
2285
|
-
}]
|
|
2286
|
-
});
|
|
2287
|
-
}
|
|
2425
|
+
if (presentImage) pendingImages.push({
|
|
2426
|
+
id: String(rawOutput.id ?? ""),
|
|
2427
|
+
name: String(rawOutput.name ?? "image"),
|
|
2428
|
+
dataUrl: String(rawOutput.dataUrl)
|
|
2429
|
+
});
|
|
2288
2430
|
if (!result.ok) success = false;
|
|
2289
2431
|
}
|
|
2432
|
+
if (cancelled || isAborted(input.signal)) {
|
|
2433
|
+
markCancelled();
|
|
2434
|
+
break;
|
|
2435
|
+
}
|
|
2436
|
+
if (pendingImages.length) messages.push({
|
|
2437
|
+
role: "user",
|
|
2438
|
+
content: [{
|
|
2439
|
+
type: "text",
|
|
2440
|
+
text: pendingImages.length === 1 ? `Attachment image for inspection: id=${pendingImages[0].id} name="${pendingImages[0].name}"` : `Attachment images for inspection:\n${pendingImages.map((img) => `- id=${img.id} name="${img.name}"`).join("\n")}`
|
|
2441
|
+
}, ...pendingImages.map((img) => ({
|
|
2442
|
+
type: "image_url",
|
|
2443
|
+
image_url: { url: img.dataUrl }
|
|
2444
|
+
}))]
|
|
2445
|
+
});
|
|
2290
2446
|
}
|
|
2291
2447
|
return {
|
|
2292
2448
|
message: {
|
|
@@ -2325,7 +2481,14 @@ function coalesceParts(parts) {
|
|
|
2325
2481
|
//#endregion
|
|
2326
2482
|
//#region src/runtime/specialists/index.ts
|
|
2327
2483
|
function systemPromptFor(role, goal, opts = {}) {
|
|
2328
|
-
const preamble = `You are a specialist in the Eclipse Docks AI runtime. Prefer tools for file and command actions. Be concise.
|
|
2484
|
+
const preamble = `You are a specialist in the Eclipse Docks AI runtime. Prefer tools for file and command actions. Be concise. After tools finish, write a short human-friendly final reply (what you did and the outcome). Do not paste tool arguments, stdout, JSON, or command logs — those already appear in the UI details panel. ${opts.hasAttachments ? [
|
|
2485
|
+
"This turn publishes session attachments under the shared in-memory root (in-memory/<sessionId>/… or in-memory://<sessionId>/…).",
|
|
2486
|
+
"Read them with workspace_read / workspace_list — in-memory paths work without mounting that root into the workspace tree.",
|
|
2487
|
+
"Do not call load_workspace / connect_opfs / connect_indexeddb to find attachments.",
|
|
2488
|
+
"Canvas from a screenshot: workspace_read the attachment path → canvas.help → workspace_write a .canvas.js (into a durable workspace folder) → canvas.open / canvas.apply_source.",
|
|
2489
|
+
"Qualify durable writes as <root>/<file> using a root from workspace_list (e.g. tests/demo.canvas.js); bare filenames are auto-rooted when a single durable folder is connected, otherwise a folder picker appears.",
|
|
2490
|
+
"If no durable workspace folder is connected for writing canvases, say so and stop."
|
|
2491
|
+
].join(" ") : "Session attachments live under the shared in-memory root and are readable via workspace_read / workspace_list."} Workspace paths use a connected root prefix from workspace_list (never write bare names at the composite root unless a single durable folder can auto-qualify them).`;
|
|
2329
2492
|
switch (role) {
|
|
2330
2493
|
case "plan": return `${preamble}\nMode: PLAN (read-only). Analyze and propose a plan for: ${goal}`;
|
|
2331
2494
|
case "explore": return `${preamble}\nMode: EXPLORE (read-only). Search and report findings for: ${goal}`;
|
|
@@ -2453,6 +2616,82 @@ function formatBucketDetail(bucket) {
|
|
|
2453
2616
|
return `${bucket.count} · ${tokens}`;
|
|
2454
2617
|
}
|
|
2455
2618
|
//#endregion
|
|
2619
|
+
//#region src/runtime/session-workspace.ts
|
|
2620
|
+
function attachmentContents(stored) {
|
|
2621
|
+
if (stored.kind === "image") {
|
|
2622
|
+
if (stored.dataUrl) return stored.dataUrl;
|
|
2623
|
+
if (stored.text) return stored.text;
|
|
2624
|
+
}
|
|
2625
|
+
return stored.text ?? "";
|
|
2626
|
+
}
|
|
2627
|
+
/** Build live-file callbacks that always re-read via the caller-provided resolver. */
|
|
2628
|
+
function attachmentLiveContents(resolve) {
|
|
2629
|
+
return {
|
|
2630
|
+
get mimeType() {
|
|
2631
|
+
return resolve()?.mime;
|
|
2632
|
+
},
|
|
2633
|
+
getContents() {
|
|
2634
|
+
const stored = resolve();
|
|
2635
|
+
if (!stored) throw new Error("Attachment payload is no longer available");
|
|
2636
|
+
return attachmentContents(stored);
|
|
2637
|
+
},
|
|
2638
|
+
size() {
|
|
2639
|
+
const stored = resolve();
|
|
2640
|
+
if (!stored) return null;
|
|
2641
|
+
if (stored.byteLength != null) return stored.byteLength;
|
|
2642
|
+
return attachmentContents(stored).length;
|
|
2643
|
+
}
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
/** Relative path under the shared in-memory root (`<sessionId>/<file>`). */
|
|
2647
|
+
function relativeAttachmentPath(sessionId, stored) {
|
|
2648
|
+
return attachmentWorkspacePath(sessionId, stored).slice(`${IN_MEMORY_ROOT_NAME}/`.length);
|
|
2649
|
+
}
|
|
2650
|
+
/**
|
|
2651
|
+
* Publish a live file node. Payload is resolved only through `resolve` — nothing is copied.
|
|
2652
|
+
*/
|
|
2653
|
+
async function syncAttachmentToWorkspace(sessionId, stored, resolve) {
|
|
2654
|
+
const root = getInMemoryRoot();
|
|
2655
|
+
const relative = relativeAttachmentPath(sessionId, stored);
|
|
2656
|
+
root.writeLiveFile(relative, attachmentLiveContents(resolve));
|
|
2657
|
+
return attachmentWorkspacePath(sessionId, stored);
|
|
2658
|
+
}
|
|
2659
|
+
async function syncAttachmentsToWorkspace(sessionId, attachments, resolveById) {
|
|
2660
|
+
for (const attachment of attachments) await syncAttachmentToWorkspace(sessionId, attachment, () => resolveById(attachment.id));
|
|
2661
|
+
}
|
|
2662
|
+
async function removeAttachmentFromWorkspace(sessionId, stored) {
|
|
2663
|
+
const root = getInMemoryRoot();
|
|
2664
|
+
const relative = relativeAttachmentPath(sessionId, stored);
|
|
2665
|
+
const resource = await root.getResource(relative);
|
|
2666
|
+
if (resource) await resource.delete();
|
|
2667
|
+
}
|
|
2668
|
+
async function removeSessionWorkspaceFolder(sessionId) {
|
|
2669
|
+
const dir = await getInMemoryRoot().getResource(sessionId);
|
|
2670
|
+
if (dir instanceof Directory) await dir.delete();
|
|
2671
|
+
}
|
|
2672
|
+
/**
|
|
2673
|
+
* Restrict the workspace-mounted in-memory view to a single folder name (active session).
|
|
2674
|
+
* Pass `null` to show the full tree again.
|
|
2675
|
+
*/
|
|
2676
|
+
function projectInMemoryMountToFolder(folderName) {
|
|
2677
|
+
if (!folderName) {
|
|
2678
|
+
setInMemoryMountProjection(null);
|
|
2679
|
+
return;
|
|
2680
|
+
}
|
|
2681
|
+
const name = folderName;
|
|
2682
|
+
setInMemoryMountProjection({
|
|
2683
|
+
async listChildren(root) {
|
|
2684
|
+
return [root.ensureDirectoryEntry(name)];
|
|
2685
|
+
},
|
|
2686
|
+
async getResource(root, path, options) {
|
|
2687
|
+
const segments = path.split("/").filter((s) => s.trim());
|
|
2688
|
+
if (segments[0] !== name) return null;
|
|
2689
|
+
if (segments.length === 1 && (path.endsWith("/") || path === name)) return root.ensureDirectoryEntry(name);
|
|
2690
|
+
return root.getResource(path, options);
|
|
2691
|
+
}
|
|
2692
|
+
});
|
|
2693
|
+
}
|
|
2694
|
+
//#endregion
|
|
2456
2695
|
//#region src/runtime/cognitive-runtime.ts
|
|
2457
2696
|
var PERSIST_KEY = "cognitive/v1/state";
|
|
2458
2697
|
/** Auto-compact after a turn when live history exceeds this many messages. */
|
|
@@ -2487,12 +2726,6 @@ var CognitiveRuntime = class {
|
|
|
2487
2726
|
emit: false
|
|
2488
2727
|
});
|
|
2489
2728
|
registerWorkspaceTools(this.tools);
|
|
2490
|
-
registerAttachmentTools(this.tools, (sessionId, attachmentId) => {
|
|
2491
|
-
const list = this.sessions.get(sessionId)?.attachments ?? [];
|
|
2492
|
-
if (!attachmentId) return list;
|
|
2493
|
-
const found = list.find((a) => a.id === attachmentId);
|
|
2494
|
-
return found ? [found] : [];
|
|
2495
|
-
});
|
|
2496
2729
|
this.tools.setApprovalMode(this.approvalMode);
|
|
2497
2730
|
this.tools.setPermissionHandler(async (req) => this.requestPermission(req));
|
|
2498
2731
|
this.ready = this.hydrateAll();
|
|
@@ -2554,7 +2787,10 @@ var CognitiveRuntime = class {
|
|
|
2554
2787
|
return catalog.find((e) => e.providerId === this.providersSettings.defaultProviderId)?.id ?? catalog[0]?.id ?? "openai/gpt-4o-mini";
|
|
2555
2788
|
}
|
|
2556
2789
|
setModelPolicy(policy) {
|
|
2557
|
-
|
|
2790
|
+
const next = normalizeModelPolicy(policy) ?? this.policy;
|
|
2791
|
+
if (sameModelPolicy(this.policy, next)) return;
|
|
2792
|
+
this.policy = next;
|
|
2793
|
+
this.persist();
|
|
2558
2794
|
}
|
|
2559
2795
|
getModelPolicy() {
|
|
2560
2796
|
return this.policy;
|
|
@@ -2616,28 +2852,46 @@ var CognitiveRuntime = class {
|
|
|
2616
2852
|
if (focused) return focused.id;
|
|
2617
2853
|
return this.sessions.createSession().id;
|
|
2618
2854
|
}
|
|
2619
|
-
/** Store attachment payloads on the session
|
|
2620
|
-
addAttachments(sessionId, attachments) {
|
|
2855
|
+
/** Store attachment payloads on the session and mirror them into in-memory/<sessionId>/. */
|
|
2856
|
+
async addAttachments(sessionId, attachments) {
|
|
2621
2857
|
const id = this.ensureSessionId(sessionId);
|
|
2622
2858
|
const session = this.sessions.get(id);
|
|
2623
2859
|
if (!session || !attachments.length) return [];
|
|
2624
2860
|
const room = Math.max(0, 8 - (session.attachments?.length ?? 0));
|
|
2625
|
-
const accepted = attachments.slice(0, room).map((attachment) =>
|
|
2861
|
+
const accepted = attachments.slice(0, room).map((attachment) => {
|
|
2862
|
+
const stored = toSessionAttachment(attachment);
|
|
2863
|
+
return {
|
|
2864
|
+
...stored,
|
|
2865
|
+
path: attachmentUri(id, stored)
|
|
2866
|
+
};
|
|
2867
|
+
});
|
|
2626
2868
|
if (!accepted.length) return [];
|
|
2627
2869
|
this.sessions.update(id, { attachments: [...session.attachments ?? [], ...accepted] });
|
|
2628
2870
|
this.commitSessions();
|
|
2871
|
+
await this.mirrorAttachments(id, accepted).catch(() => void 0);
|
|
2629
2872
|
return accepted;
|
|
2630
2873
|
}
|
|
2631
|
-
/** Remove an attachment payload from the session store. */
|
|
2874
|
+
/** Remove an attachment payload from the session store and in-memory workspace. */
|
|
2632
2875
|
removeAttachment(sessionId, attachmentId) {
|
|
2633
2876
|
const session = this.sessions.get(sessionId);
|
|
2634
2877
|
if (!session) return false;
|
|
2878
|
+
const removed = (session.attachments ?? []).find((a) => a.id === attachmentId);
|
|
2635
2879
|
const next = (session.attachments ?? []).filter((a) => a.id !== attachmentId);
|
|
2636
2880
|
if (next.length === (session.attachments?.length ?? 0)) return false;
|
|
2637
2881
|
this.sessions.update(sessionId, { attachments: next });
|
|
2638
2882
|
this.commitSessions();
|
|
2883
|
+
if (removed) removeAttachmentFromWorkspace(sessionId, removed);
|
|
2639
2884
|
return true;
|
|
2640
2885
|
}
|
|
2886
|
+
/** Drop a session and its in-memory attachment folder. */
|
|
2887
|
+
removeSession(sessionId) {
|
|
2888
|
+
const ok = this.sessions.remove(sessionId);
|
|
2889
|
+
if (ok) {
|
|
2890
|
+
removeSessionWorkspaceFolder(sessionId);
|
|
2891
|
+
this.commitSessions();
|
|
2892
|
+
}
|
|
2893
|
+
return ok;
|
|
2894
|
+
}
|
|
2641
2895
|
/**
|
|
2642
2896
|
* Compact a session: drop older turns, store a summary (LLM when possible),
|
|
2643
2897
|
* and persist. The summary is injected into later model calls.
|
|
@@ -2681,8 +2935,17 @@ var CognitiveRuntime = class {
|
|
|
2681
2935
|
const session = (input.sessionId ? this.sessions.get(input.sessionId) : this.sessions.focused()) ?? this.sessions.createSession();
|
|
2682
2936
|
const text = input.text.trim();
|
|
2683
2937
|
const messageId = `msg-${Date.now()}-u`;
|
|
2684
|
-
const fresh = (input.attachments ?? []).slice(0, 8).map((attachment) =>
|
|
2685
|
-
|
|
2938
|
+
const fresh = (input.attachments ?? []).slice(0, 8).map((attachment) => {
|
|
2939
|
+
const stored = toSessionAttachment(attachment, { messageId });
|
|
2940
|
+
return {
|
|
2941
|
+
...stored,
|
|
2942
|
+
path: attachmentUri(session.id, stored)
|
|
2943
|
+
};
|
|
2944
|
+
});
|
|
2945
|
+
if (fresh.length) {
|
|
2946
|
+
this.sessions.update(session.id, { attachments: [...this.sessions.get(session.id)?.attachments ?? [], ...fresh] });
|
|
2947
|
+
await this.mirrorAttachments(session.id, fresh);
|
|
2948
|
+
}
|
|
2686
2949
|
const existingIds = (input.attachmentIds ?? []).filter(Boolean);
|
|
2687
2950
|
const stored = this.sessions.get(session.id)?.attachments ?? [];
|
|
2688
2951
|
const byId = new Map(stored.map((a) => [a.id, a]));
|
|
@@ -2693,12 +2956,16 @@ var CognitiveRuntime = class {
|
|
|
2693
2956
|
if (found && !published.some((p) => p.id === found.id)) published.push(found);
|
|
2694
2957
|
}
|
|
2695
2958
|
if (!text && !published.length) throw new Error("Prompt requires text or at least one attachment");
|
|
2959
|
+
if (published.length) await this.mirrorAttachments(session.id, published).catch(() => void 0);
|
|
2696
2960
|
const parts = [];
|
|
2697
2961
|
if (text) parts.push({
|
|
2698
2962
|
type: "text",
|
|
2699
2963
|
text
|
|
2700
2964
|
});
|
|
2701
|
-
for (const item of published) parts.push(toFileRefPart(
|
|
2965
|
+
for (const item of published) parts.push(toFileRefPart({
|
|
2966
|
+
...item,
|
|
2967
|
+
path: item.path || attachmentUri(session.id, item)
|
|
2968
|
+
}));
|
|
2702
2969
|
const promptSummary = summarizePrompt(text, published.map((a) => ({
|
|
2703
2970
|
name: a.name,
|
|
2704
2971
|
kind: a.kind
|
|
@@ -2802,7 +3069,7 @@ var CognitiveRuntime = class {
|
|
|
2802
3069
|
sessionId: session.id,
|
|
2803
3070
|
activation: .7
|
|
2804
3071
|
});
|
|
2805
|
-
if ((this.sessions.get(session.id)?.history.length ?? 0) > AUTO_COMPACT_AFTER) await this.compactSession(session.id, COMPACT_KEEP_COUNT, input.signal);
|
|
3072
|
+
if (!Boolean(input.signal?.aborted) && (this.sessions.get(session.id)?.history.length ?? 0) > AUTO_COMPACT_AFTER) await this.compactSession(session.id, COMPACT_KEEP_COUNT, input.signal);
|
|
2806
3073
|
this.sessions.update(session.id, { status: "idle" });
|
|
2807
3074
|
this.events.publish(TOPIC.messageDone, {
|
|
2808
3075
|
sessionId: session.id,
|
|
@@ -2863,11 +3130,17 @@ var CognitiveRuntime = class {
|
|
|
2863
3130
|
});
|
|
2864
3131
|
}
|
|
2865
3132
|
emitSessions() {
|
|
3133
|
+
const focusedId = this.sessions.focused()?.id ?? null;
|
|
3134
|
+
projectInMemoryMountToFolder(focusedId);
|
|
2866
3135
|
this.events.publish(TOPIC.sessionChanged, {
|
|
2867
3136
|
sessions: this.sessions.list(),
|
|
2868
|
-
focusedId
|
|
3137
|
+
focusedId
|
|
2869
3138
|
});
|
|
2870
3139
|
}
|
|
3140
|
+
/** Live in-memory file refs that resolve payloads from the session store. */
|
|
3141
|
+
mirrorAttachments(sessionId, attachments) {
|
|
3142
|
+
return syncAttachmentsToWorkspace(sessionId, attachments, (attachmentId) => this.sessions.get(sessionId)?.attachments?.find((a) => a.id === attachmentId));
|
|
3143
|
+
}
|
|
2871
3144
|
async hydrateAll() {
|
|
2872
3145
|
await loadModelsCatalog().catch(() => void 0);
|
|
2873
3146
|
await this.hydrateProviders();
|
|
@@ -2879,6 +3152,16 @@ var CognitiveRuntime = class {
|
|
|
2879
3152
|
if (!this.sessions.list().length) this.sessions.createSession({ title: "Chat" });
|
|
2880
3153
|
const mostRecent = this.sessions.list()[0];
|
|
2881
3154
|
if (mostRecent) this.sessions.focus(mostRecent.id);
|
|
3155
|
+
for (const session of this.sessions.list()) {
|
|
3156
|
+
const list = session.attachments ?? [];
|
|
3157
|
+
if (!list.length) continue;
|
|
3158
|
+
const stamped = list.map((a) => a.path ? a : {
|
|
3159
|
+
...a,
|
|
3160
|
+
path: attachmentUri(session.id, a)
|
|
3161
|
+
});
|
|
3162
|
+
if (stamped.some((a, i) => a.path !== list[i]?.path)) this.sessions.update(session.id, { attachments: stamped });
|
|
3163
|
+
await this.mirrorAttachments(session.id, stamped).catch(() => void 0);
|
|
3164
|
+
}
|
|
2882
3165
|
this.emitSessions();
|
|
2883
3166
|
}
|
|
2884
3167
|
async hydrateProviders() {
|
|
@@ -2906,6 +3189,8 @@ var CognitiveRuntime = class {
|
|
|
2906
3189
|
this.approvalMode = saved.approvalMode;
|
|
2907
3190
|
this.tools.setApprovalMode(saved.approvalMode);
|
|
2908
3191
|
}
|
|
3192
|
+
const policy = normalizeModelPolicy(saved.modelPolicy);
|
|
3193
|
+
if (policy) this.policy = policy;
|
|
2909
3194
|
} catch {}
|
|
2910
3195
|
}
|
|
2911
3196
|
async persist() {
|
|
@@ -2913,7 +3198,8 @@ var CognitiveRuntime = class {
|
|
|
2913
3198
|
await persistenceService.persistObject(PERSIST_KEY, {
|
|
2914
3199
|
sessions: this.sessions.snapshot(),
|
|
2915
3200
|
memory: this.memory.snapshot(),
|
|
2916
|
-
approvalMode: this.approvalMode
|
|
3201
|
+
approvalMode: this.approvalMode,
|
|
3202
|
+
modelPolicy: this.policy
|
|
2917
3203
|
});
|
|
2918
3204
|
} catch {}
|
|
2919
3205
|
}
|
|
@@ -2921,6 +3207,32 @@ var CognitiveRuntime = class {
|
|
|
2921
3207
|
function isToolApprovalMode(value) {
|
|
2922
3208
|
return value === "manual" || value === "auto_safe" || value === "auto_all";
|
|
2923
3209
|
}
|
|
3210
|
+
function isOptimizeFor(value) {
|
|
3211
|
+
return value === "cost" || value === "balance" || value === "intelligence";
|
|
3212
|
+
}
|
|
3213
|
+
function isEffortOrAuto(value) {
|
|
3214
|
+
return value === "auto" || value === "none" || value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max";
|
|
3215
|
+
}
|
|
3216
|
+
function normalizeModelPolicy(value) {
|
|
3217
|
+
if (!value || typeof value !== "object") return null;
|
|
3218
|
+
const raw = value;
|
|
3219
|
+
if (raw.mode === "auto" && isOptimizeFor(raw.optimizeFor)) return {
|
|
3220
|
+
mode: "auto",
|
|
3221
|
+
optimizeFor: raw.optimizeFor
|
|
3222
|
+
};
|
|
3223
|
+
if (raw.mode === "manual" && typeof raw.model === "string" && raw.model.trim()) return {
|
|
3224
|
+
mode: "manual",
|
|
3225
|
+
model: raw.model.trim(),
|
|
3226
|
+
...isEffortOrAuto(raw.effort) ? { effort: raw.effort } : {}
|
|
3227
|
+
};
|
|
3228
|
+
return null;
|
|
3229
|
+
}
|
|
3230
|
+
function sameModelPolicy(a, b) {
|
|
3231
|
+
if (a.mode !== b.mode) return false;
|
|
3232
|
+
if (a.mode === "auto" && b.mode === "auto") return a.optimizeFor === b.optimizeFor;
|
|
3233
|
+
if (a.mode === "manual" && b.mode === "manual") return a.model === b.model && (a.effort ?? "auto") === (b.effort ?? "auto");
|
|
3234
|
+
return false;
|
|
3235
|
+
}
|
|
2924
3236
|
function externalCatalogEntry(id, providerId) {
|
|
2925
3237
|
return {
|
|
2926
3238
|
id,
|
|
@@ -2949,4 +3261,4 @@ function summarizePrompt(text, attachments) {
|
|
|
2949
3261
|
//#endregion
|
|
2950
3262
|
export { AI_SETTINGS_KEY as _, formatContextPercent as a, TOPIC as b, readPromptAttachmentFromFile as c, defaultProvidersSettings as d, listConfiguredModelIds as f, AI_SETTINGS_EDITOR_ID as g, loadModelsCatalog as h, formatBucketDetail as i, OpenAICompatibleProvider as l, detectEffortCapability as m, cognitiveRuntime as n, formatTokenCount as o, parseModelIds as p, estimateSessionContextUsage as r, ATTACHMENT_FILE_ACCEPT as s, CognitiveRuntime as t, fetchOpenAICompatibleModels as u, AI_VIEW_NAME as v, OPEN_AI_SETTINGS_COMMAND as y };
|
|
2951
3263
|
|
|
2952
|
-
//# sourceMappingURL=runtime-
|
|
3264
|
+
//# sourceMappingURL=runtime-By5PP2BC.js.map
|