@chanlerdev/scorel 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.md +55 -2
  2. package/dist/index.js +395 -31
  3. package/dist/index.js.map +3 -3
  4. package/docs/CHANGELOG.md +53 -0
  5. package/docs/ROADMAP.md +19 -0
  6. package/docs/spec/channels.md +15 -5
  7. package/docs/spec/ship/S0087-gui-ui-polish-sweep.md +153 -0
  8. package/docs/spec/ship/S0088-gui-streaming-thinking-contract.md +35 -0
  9. package/docs/spec/ship/S0089-memory-reliability-and-dream-trigger.md +84 -0
  10. package/docs/spec/ship/S0090-gui-provider-delete-and-dark-code-theme.md +77 -0
  11. package/docs/spec/ship/S0091-built-in-qq-and-wechat-im-extensions.md +125 -0
  12. package/docs/spec/ship/S0092-im-message-media-and-human-cadence.md +83 -0
  13. package/docs/spec/ship/S0093-gui-im-settings-platform-layout.md +66 -0
  14. package/docs/spec/ship/S0094-im-inbound-runtime.md +67 -0
  15. package/docs/spec/ship/S0095-gui-im-session-list-refresh.md +36 -0
  16. package/extensions/builtin/loopback/skills/loopback/SKILL.md +2 -0
  17. package/extensions/builtin/qq/adapter.d.ts +27 -0
  18. package/extensions/builtin/qq/adapter.js +384 -0
  19. package/extensions/builtin/qq/scorel.extension.json +7 -0
  20. package/extensions/builtin/qq/skills/qq/SKILL.md +9 -0
  21. package/extensions/builtin/telegram/adapter.d.ts +1 -1
  22. package/extensions/builtin/telegram/adapter.js +7 -0
  23. package/extensions/builtin/telegram/skills/telegram/SKILL.md +2 -0
  24. package/extensions/builtin/wechat/adapter.d.ts +24 -0
  25. package/extensions/builtin/wechat/adapter.js +226 -0
  26. package/extensions/builtin/wechat/scorel.extension.json +7 -0
  27. package/extensions/builtin/wechat/skills/wechat/SKILL.md +9 -0
  28. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -257,10 +257,18 @@ var init_src2 = __esm({
257
257
  this.#assertDaemonConnected();
258
258
  return (await this.#request("fetch_provider_models", input)).models;
259
259
  }
260
+ async removeModelProvider(input) {
261
+ this.#assertDaemonConnected();
262
+ return this.#request("remove_model_provider", input);
263
+ }
260
264
  async getMemorySettings(input) {
261
265
  this.#assertDaemonConnected();
262
266
  return (await this.#request("get_memory_settings", input)).memory;
263
267
  }
268
+ async getMemoryStatus(input) {
269
+ this.#assertDaemonConnected();
270
+ return (await this.#request("get_memory_status", input)).status;
271
+ }
264
272
  async upsertMemorySettings(input) {
265
273
  this.#assertDaemonConnected();
266
274
  return (await this.#request("upsert_memory_settings", input)).memory;
@@ -799,7 +807,7 @@ var init_sessions = __esm({
799
807
  // packages/core/src/config/index.ts
800
808
  import { readFile as readFile3 } from "node:fs/promises";
801
809
  import { join as join4 } from "node:path";
802
- var SCOREL_CONFIG_SCHEMA, scorelUserRoot, scorelUserConfigPath, scorelSessionsDir, scorelProjectConfigPath, loadScorelConfig, loadScorelConfigProfile, listProviderConnections, listAvailableModels, listProviderModels, resolveModelSelection, renderModelProfileConfig, renderMemoryConfig, renderExtensionConfig, DEFAULT_MEMORY_CONFIG, loadMemory, loadExtensions, loadProviders, loadProviderProfiles, loadProviderModels, loadAvailableModels, loadRoles, readConfigText, parseToml, parseEditableConfig, renderRawConfig, emptyRawConfig, stripComment, requireString, normalizeProviderName, requireProviderCredential, resolveProviderApiKey, providerCredentialSummary, requireNumber, requireNonNegativeNumber, requireCompactThreshold, requireBoolean, requireCustomApi, requireProviderType, requireSection, ensureSection, setConfigValue, assertKnownKey, setValue, parseTomlValue, stripTrailingSlashes, requireIdentifier, tomlString, renderTomlValue, requireModelRole, modelRoles;
810
+ var SCOREL_CONFIG_SCHEMA, scorelUserRoot, scorelUserConfigPath, scorelSessionsDir, scorelProjectConfigPath, loadScorelConfig, loadScorelConfigProfile, listProviderConnections, listAvailableModels, listProviderModels, resolveModelSelection, renderModelProfileConfig, removeProvider, renderMemoryConfig, renderExtensionConfig, DEFAULT_MEMORY_CONFIG, loadMemory, loadExtensions, loadProviders, loadProviderProfiles, loadProviderModels, loadAvailableModels, loadRoles, readConfigText, parseToml, parseEditableConfig, renderRawConfig, emptyRawConfig, stripComment, requireString, normalizeProviderName, requireProviderCredential, resolveProviderApiKey, providerCredentialSummary, requireNumber, requireNonNegativeNumber, requireCompactThreshold, requireBoolean, requireCustomApi, requireProviderType, requireSection, ensureSection, setConfigValue, assertKnownKey, setValue, parseTomlValue, stripTrailingSlashes, requireIdentifier, tomlString, renderTomlValue, requireModelRole, modelRoles;
803
811
  var init_config = __esm({
804
812
  "packages/core/src/config/index.ts"() {
805
813
  "use strict";
@@ -974,6 +982,9 @@ var init_config = __esm({
974
982
  };
975
983
  renderModelProfileConfig = (input) => {
976
984
  const raw = parseEditableConfig(input.existingConfigText);
985
+ if (input.removeProviderId) {
986
+ removeProvider(raw, requireIdentifier(input.removeProviderId, "removeProviderId"));
987
+ }
977
988
  if (input.providerType || input.provider || input.apiKeyEnv || input.apiKey || input.api || input.baseUrl) {
978
989
  const providerId = requireIdentifier(input.providerId, "providerId");
979
990
  const providerType = requireProviderType(input.providerType, "providerType");
@@ -1081,6 +1092,34 @@ var init_config = __esm({
1081
1092
  }
1082
1093
  return renderRawConfig(raw);
1083
1094
  };
1095
+ removeProvider = (raw, providerId) => {
1096
+ delete raw.providers[providerId];
1097
+ const removedProviderModels = /* @__PURE__ */ new Set();
1098
+ for (const [providerModelId, providerModel] of Object.entries(raw.providerModels)) {
1099
+ if (providerModel.provider === providerId) {
1100
+ delete raw.providerModels[providerModelId];
1101
+ removedProviderModels.add(providerModelId);
1102
+ }
1103
+ }
1104
+ const removedAvailableModels = /* @__PURE__ */ new Set();
1105
+ for (const [availableModelId, availableModel] of Object.entries(raw.availableModels)) {
1106
+ if (availableModel.model && removedProviderModels.has(availableModel.model)) {
1107
+ delete raw.availableModels[availableModelId];
1108
+ removedAvailableModels.add(availableModelId);
1109
+ }
1110
+ }
1111
+ if (!raw.modelProfile?.roles) return;
1112
+ const fallbackModelId = Object.keys(raw.availableModels).sort()[0];
1113
+ if (!fallbackModelId) {
1114
+ delete raw.modelProfile;
1115
+ return;
1116
+ }
1117
+ for (const role of ["primary", "standard", "auxiliary"]) {
1118
+ if (!raw.modelProfile.roles[role] || removedAvailableModels.has(raw.modelProfile.roles[role])) {
1119
+ raw.modelProfile.roles[role] = fallbackModelId;
1120
+ }
1121
+ }
1122
+ };
1084
1123
  renderMemoryConfig = (input) => {
1085
1124
  const raw = parseEditableConfig(input.existingConfigText);
1086
1125
  raw.memory = {
@@ -2413,7 +2452,7 @@ var init_tools = __esm({
2413
2452
  });
2414
2453
 
2415
2454
  // packages/core/src/channel/index.ts
2416
- var createSendChannelMessageTool, parseSendChannelMessageInput, isRecord3;
2455
+ var createSendChannelMessageTool, parseSendChannelMessageInput, parseAttachments, optionalString2, isRecord3;
2417
2456
  var init_channel = __esm({
2418
2457
  "packages/core/src/channel/index.ts"() {
2419
2458
  "use strict";
@@ -2426,7 +2465,7 @@ var init_channel = __esm({
2426
2465
  const result = await options.sendCurrent(input);
2427
2466
  return {
2428
2467
  content: [{ type: "text", text: `Channel message sent to ${result.channel}:${result.target}` }],
2429
- details: result
2468
+ details: { ...result, attachments: result.attachments ?? input.attachments?.length ?? 0 }
2430
2469
  };
2431
2470
  }
2432
2471
  });
@@ -2434,8 +2473,10 @@ var init_channel = __esm({
2434
2473
  if (!isRecord3(value)) {
2435
2474
  throw new Error("SendChannelMessage args must be an object");
2436
2475
  }
2437
- if (typeof value.text !== "string" || value.text.trim().length === 0) {
2438
- throw new Error("SendChannelMessage.text must be a non-empty string");
2476
+ const text = typeof value.text === "string" && value.text.trim().length > 0 ? value.text : void 0;
2477
+ const attachments = parseAttachments(value.attachments);
2478
+ if (!text && attachments.length === 0) {
2479
+ throw new Error("SendChannelMessage requires text or attachments");
2439
2480
  }
2440
2481
  if (value.channel !== void 0 && (typeof value.channel !== "string" || value.channel.trim().length === 0)) {
2441
2482
  throw new Error("SendChannelMessage.channel must be a non-empty string when provided");
@@ -2444,11 +2485,49 @@ var init_channel = __esm({
2444
2485
  throw new Error("SendChannelMessage.target must be current when provided");
2445
2486
  }
2446
2487
  return {
2447
- text: value.text,
2488
+ ...text ? { text } : {},
2489
+ ...attachments.length > 0 ? { attachments } : {},
2448
2490
  ...typeof value.channel === "string" ? { channel: value.channel } : {},
2449
2491
  ...value.target === "current" ? { target: "current" } : {}
2450
2492
  };
2451
2493
  };
2494
+ parseAttachments = (value) => {
2495
+ if (value === void 0) {
2496
+ return [];
2497
+ }
2498
+ if (!Array.isArray(value)) {
2499
+ throw new Error("SendChannelMessage.attachments must be an array");
2500
+ }
2501
+ return value.map((item, index) => {
2502
+ if (!isRecord3(item)) {
2503
+ throw new Error(`SendChannelMessage.attachments.${index} must be an object`);
2504
+ }
2505
+ if (item.type !== "image" && item.type !== "file") {
2506
+ throw new Error(`SendChannelMessage.attachments.${index}.type must be image or file`);
2507
+ }
2508
+ const path = optionalString2(item.path, `SendChannelMessage.attachments.${index}.path`);
2509
+ const url = optionalString2(item.url, `SendChannelMessage.attachments.${index}.url`);
2510
+ if (!path && !url) {
2511
+ throw new Error(`SendChannelMessage.attachments.${index} requires path or url`);
2512
+ }
2513
+ return {
2514
+ type: item.type,
2515
+ ...path ? { path } : {},
2516
+ ...url ? { url } : {},
2517
+ ...optionalString2(item.mimeType, `SendChannelMessage.attachments.${index}.mimeType`) ? { mimeType: optionalString2(item.mimeType, `SendChannelMessage.attachments.${index}.mimeType`) } : {},
2518
+ ...optionalString2(item.caption, `SendChannelMessage.attachments.${index}.caption`) ? { caption: optionalString2(item.caption, `SendChannelMessage.attachments.${index}.caption`) } : {}
2519
+ };
2520
+ });
2521
+ };
2522
+ optionalString2 = (value, name) => {
2523
+ if (value === void 0 || value === "") {
2524
+ return void 0;
2525
+ }
2526
+ if (typeof value !== "string") {
2527
+ throw new Error(`${name} must be a string`);
2528
+ }
2529
+ return value;
2530
+ };
2452
2531
  isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2453
2532
  }
2454
2533
  });
@@ -2669,7 +2748,7 @@ var init_instructions = __esm({
2669
2748
  import { appendFile, mkdir as mkdir3, readFile as readFile7, writeFile as writeFile3 } from "node:fs/promises";
2670
2749
  import { homedir as homedir3 } from "node:os";
2671
2750
  import { join as join6 } from "node:path";
2672
- var memoryDate, scorelMemoryPaths, scorelSessionMemoryPaths, buildMemoryContext, renderMemoryHarness, appendDailyEntry, createAppendDailyTool, renderDailyEntry, readSessionMemory, writeSessionMemory, renderSessionMemory, ensureMemoryFiles, ensureFile, readOptional, trimForContext, compactLine, renderList, renderBullets, normalizeMarkdownFile, parseAppendDailyInput, requireString3, optionalStringArray, isRecord5, safeProjectId, isNodeErrorCode2;
2751
+ var memoryDate, scorelMemoryPaths, scorelSessionMemoryPaths, buildMemoryContext, renderMemoryHarness, appendDailyEntry, createAppendDailyTool, renderDailyEntry, readMemoryDreamState, writeMemoryDreamState, readSessionMemory, writeSessionMemory, renderSessionMemory, ensureMemoryFiles, ensureFile, readOptional, trimForContext, compactLine, renderList, renderBullets, normalizeMarkdownFile, parseAppendDailyInput, validateAppendDailyInput, isLowSignalSummary, containsNormalizedDailyEntry, normalizeDailyText, requireString3, optionalStringArray, optionalNumber2, optionalString3, parseLastFailure, isRecord5, safeProjectId, isNodeErrorCode2;
2673
2752
  var init_memory = __esm({
2674
2753
  "packages/core/src/memory/index.ts"() {
2675
2754
  "use strict";
@@ -2739,7 +2818,11 @@ var init_memory = __esm({
2739
2818
  await ensureMemoryFiles(paths);
2740
2819
  const text = options.text.trim();
2741
2820
  if (!text) {
2742
- return { path: paths.todayDailyPath, entry: "", date: paths.today };
2821
+ return { path: paths.todayDailyPath, entry: "", date: paths.today, skippedReason: "empty" };
2822
+ }
2823
+ const existing = await readOptional(paths.todayDailyPath);
2824
+ if (containsNormalizedDailyEntry(existing, text)) {
2825
+ return { path: paths.todayDailyPath, entry: "", date: paths.today, skippedReason: "duplicate" };
2743
2826
  }
2744
2827
  const time = new Date((options.now ?? Date.now)()).toISOString().slice(11, 16);
2745
2828
  const entry = `- ${time} ${text.replace(/\s+/g, " ")}
@@ -2756,6 +2839,7 @@ var init_memory = __esm({
2756
2839
  ].join(" "),
2757
2840
  execute: async (_toolCallId, args) => {
2758
2841
  const input = parseAppendDailyInput(args);
2842
+ validateAppendDailyInput(input);
2759
2843
  const result = await appendDailyEntry({
2760
2844
  projectId: options.projectId,
2761
2845
  homeDir: options.homeDir,
@@ -2766,11 +2850,12 @@ var init_memory = __esm({
2766
2850
  return {
2767
2851
  content: [{
2768
2852
  type: "text",
2769
- text: result.entry ? `Daily appended: ${result.date}` : "Daily append skipped: empty entry"
2853
+ text: result.entry ? `Daily appended: ${result.date}` : `Daily append skipped: ${result.skippedReason ?? "empty"}`
2770
2854
  }],
2771
2855
  details: {
2772
2856
  path: result.path,
2773
- date: result.date
2857
+ date: result.date,
2858
+ skippedReason: result.skippedReason
2774
2859
  }
2775
2860
  };
2776
2861
  }
@@ -2781,10 +2866,45 @@ var init_memory = __esm({
2781
2866
  renderList("Completed", input.completed),
2782
2867
  renderList("Decisions", input.decisions),
2783
2868
  renderList("Follow-ups", input.followUps),
2784
- renderList("Memory candidates", input.memoryCandidates)
2869
+ renderList("Memory candidates", input.memoryCandidates),
2870
+ renderList("Evidence", input.evidence)
2785
2871
  ].filter(Boolean);
2786
2872
  return sections.join(" ");
2787
2873
  };
2874
+ readMemoryDreamState = async (options) => {
2875
+ const paths = scorelMemoryPaths(options);
2876
+ const text = await readOptional(paths.dreamStatePath);
2877
+ if (!text.trim()) return void 0;
2878
+ try {
2879
+ const parsed = JSON.parse(text);
2880
+ if (parsed.projectId !== options.projectId) return void 0;
2881
+ return {
2882
+ projectId: options.projectId,
2883
+ dirty: Boolean(parsed.dirty),
2884
+ running: Boolean(parsed.running),
2885
+ sessionId: optionalString3(parsed.sessionId),
2886
+ clientId: optionalString3(parsed.clientId),
2887
+ lastDailyAppendAt: optionalNumber2(parsed.lastDailyAppendAt),
2888
+ lastDailyPath: optionalString3(parsed.lastDailyPath),
2889
+ scheduledFor: optionalNumber2(parsed.scheduledFor),
2890
+ lastAttemptAt: optionalNumber2(parsed.lastAttemptAt),
2891
+ lastSuccessAt: optionalNumber2(parsed.lastSuccessAt),
2892
+ lastFailure: parseLastFailure(parsed.lastFailure),
2893
+ lastProjectMemoryUpdateAt: optionalNumber2(parsed.lastProjectMemoryUpdateAt),
2894
+ lastRootMemoryUpdateAt: optionalNumber2(parsed.lastRootMemoryUpdateAt)
2895
+ };
2896
+ } catch {
2897
+ return void 0;
2898
+ }
2899
+ };
2900
+ writeMemoryDreamState = async (options) => {
2901
+ const paths = scorelMemoryPaths(options);
2902
+ await mkdir3(paths.projectDir, { recursive: true, mode: 448 });
2903
+ const state = { ...options.state, projectId: options.projectId };
2904
+ await writeFile3(paths.dreamStatePath, `${JSON.stringify(state, null, 2)}
2905
+ `, { encoding: "utf8", mode: 384 });
2906
+ return state;
2907
+ };
2788
2908
  readSessionMemory = async (options) => {
2789
2909
  const paths = scorelSessionMemoryPaths(options);
2790
2910
  return trimForContext(await readOptional(paths.sessionMemoryPath), 12e3, "tail");
@@ -2869,9 +2989,45 @@ var init_memory = __esm({
2869
2989
  completed: optionalStringArray(value.completed, "completed"),
2870
2990
  decisions: optionalStringArray(value.decisions, "decisions"),
2871
2991
  followUps: optionalStringArray(value.followUps, "followUps"),
2872
- memoryCandidates: optionalStringArray(value.memoryCandidates, "memoryCandidates")
2992
+ memoryCandidates: optionalStringArray(value.memoryCandidates, "memoryCandidates"),
2993
+ evidence: optionalStringArray(value.evidence, "evidence")
2873
2994
  };
2874
2995
  };
2996
+ validateAppendDailyInput = (input) => {
2997
+ const summary = compactLine(input.summary, 500);
2998
+ if (isLowSignalSummary(summary)) {
2999
+ throw new Error("AppendDaily.summary is too generic; include concrete durable progress or a decision");
3000
+ }
3001
+ const details = [
3002
+ ...input.completed ?? [],
3003
+ ...input.decisions ?? [],
3004
+ ...input.followUps ?? [],
3005
+ ...input.memoryCandidates ?? [],
3006
+ ...input.evidence ?? []
3007
+ ].map((value) => compactLine(value, 240)).filter(Boolean);
3008
+ if (details.length === 0) {
3009
+ throw new Error("AppendDaily requires at least one completed item, decision, follow-up, memory candidate, or evidence item");
3010
+ }
3011
+ };
3012
+ isLowSignalSummary = (value) => {
3013
+ const normalized = value.toLowerCase().replace(/\s+/g, "");
3014
+ return [
3015
+ "done",
3016
+ "completed",
3017
+ "finished",
3018
+ "updated",
3019
+ "\u7EE7\u7EED\u63A8\u8FDB",
3020
+ "\u5B8C\u6210\u4EFB\u52A1",
3021
+ "\u5DF2\u5904\u7406",
3022
+ "\u5904\u7406\u5B8C\u6210",
3023
+ "\u505A\u4E86\u4E00\u4E9B\u4FEE\u6539"
3024
+ ].includes(normalized);
3025
+ };
3026
+ containsNormalizedDailyEntry = (daily, text) => {
3027
+ const needle = normalizeDailyText(text);
3028
+ return daily.split("\n").map((line) => line.replace(/^-\s+\d\d:\d\d\s+/, "")).some((line) => normalizeDailyText(line) === needle);
3029
+ };
3030
+ normalizeDailyText = (value) => value.replace(/\s+/g, " ").trim().toLowerCase();
2875
3031
  requireString3 = (value, name) => {
2876
3032
  if (typeof value !== "string" || !value.trim()) {
2877
3033
  throw new Error(`AppendDaily.${name} must be a non-empty string`);
@@ -2887,6 +3043,14 @@ var init_memory = __esm({
2887
3043
  }
2888
3044
  return value.map((item) => item.trim()).filter(Boolean);
2889
3045
  };
3046
+ optionalNumber2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
3047
+ optionalString3 = (value) => typeof value === "string" && value.trim() ? value : void 0;
3048
+ parseLastFailure = (value) => {
3049
+ if (!isRecord5(value)) return void 0;
3050
+ const at = optionalNumber2(value.at);
3051
+ const message = optionalString3(value.message);
3052
+ return at !== void 0 && message ? { at, message } : void 0;
3053
+ };
2890
3054
  isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2891
3055
  safeProjectId = (projectId) => {
2892
3056
  if (!/^[A-Za-z0-9_-]+$/.test(projectId)) {
@@ -2921,6 +3085,8 @@ var init_pi_ai = __esm({
2921
3085
  for await (const event of stream) {
2922
3086
  if (event.type === "text_delta") {
2923
3087
  yield { type: "text_delta", delta: event.delta };
3088
+ } else if (event.type === "thinking_delta") {
3089
+ yield { type: "thinking_delta", delta: event.delta };
2924
3090
  }
2925
3091
  }
2926
3092
  return fromPiAssistant(await stream.result());
@@ -3111,7 +3277,8 @@ var init_pi_ai = __esm({
3111
3277
  completed: Type.Optional(Type.Array(Type.String())),
3112
3278
  decisions: Type.Optional(Type.Array(Type.String())),
3113
3279
  followUps: Type.Optional(Type.Array(Type.String())),
3114
- memoryCandidates: Type.Optional(Type.Array(Type.String()))
3280
+ memoryCandidates: Type.Optional(Type.Array(Type.String())),
3281
+ evidence: Type.Optional(Type.Array(Type.String()))
3115
3282
  });
3116
3283
  case "Skill":
3117
3284
  return Type.Object({
@@ -3250,6 +3417,7 @@ var init_runtime = __esm({
3250
3417
  }
3251
3418
  async *#runProviderTurn(context, systemPrompt, options, signal) {
3252
3419
  let text = "";
3420
+ let thinking = "";
3253
3421
  yield { type: "message_start", role: "assistant" };
3254
3422
  try {
3255
3423
  const stream = this.#provider.streamTurn({
@@ -3265,7 +3433,7 @@ var init_runtime = __esm({
3265
3433
  }
3266
3434
  const next = await stream.next();
3267
3435
  if (next.done) {
3268
- const message = normalizeAssistantMessage(next.value, text, signal.aborted ? "cancelled" : "end_turn");
3436
+ const message = normalizeAssistantMessage(next.value, { thinking, text }, signal.aborted ? "cancelled" : "end_turn");
3269
3437
  if (message) {
3270
3438
  yield { type: "message_end", message };
3271
3439
  }
@@ -3274,16 +3442,19 @@ var init_runtime = __esm({
3274
3442
  if (next.value.type === "text_delta") {
3275
3443
  text += next.value.delta;
3276
3444
  yield next.value;
3445
+ } else if (next.value.type === "thinking_delta") {
3446
+ thinking += next.value.delta;
3447
+ yield next.value;
3277
3448
  }
3278
3449
  }
3279
- const cancelledMessage = partialAssistantMessage(text, "cancelled");
3450
+ const cancelledMessage = partialAssistantMessage({ thinking, text }, "cancelled");
3280
3451
  if (cancelledMessage) {
3281
3452
  yield { type: "message_end", message: cancelledMessage };
3282
3453
  }
3283
3454
  return { stopReason: "cancelled" };
3284
3455
  } catch (cause) {
3285
3456
  const error = cause instanceof Error ? cause : new Error(String(cause));
3286
- const partial = partialAssistantMessage(text, "error");
3457
+ const partial = partialAssistantMessage({ thinking, text }, "error");
3287
3458
  if (partial) {
3288
3459
  yield { type: "message_end", message: partial };
3289
3460
  }
@@ -3334,23 +3505,26 @@ var init_runtime = __esm({
3334
3505
  };
3335
3506
  }
3336
3507
  };
3337
- normalizeAssistantMessage = (value, text, fallbackStopReason) => {
3508
+ normalizeAssistantMessage = (value, streamed, fallbackStopReason) => {
3338
3509
  if (value) {
3339
3510
  if (!isAssistantMessage(value)) {
3340
3511
  throw new Error(`Provider returned ${value.role} message instead of assistant`);
3341
3512
  }
3342
3513
  return value;
3343
3514
  }
3344
- return partialAssistantMessage(text, fallbackStopReason);
3515
+ return partialAssistantMessage(streamed, fallbackStopReason);
3345
3516
  };
3346
3517
  isAssistantMessage = (message) => message.role === "assistant";
3347
- partialAssistantMessage = (text, stopReason) => {
3348
- if (text.length === 0) {
3518
+ partialAssistantMessage = (streamed, stopReason) => {
3519
+ if (streamed.thinking.length === 0 && streamed.text.length === 0) {
3349
3520
  return void 0;
3350
3521
  }
3351
3522
  return {
3352
3523
  role: "assistant",
3353
- content: [{ type: "text", text }],
3524
+ content: [
3525
+ ...streamed.thinking ? [{ type: "thinking", text: streamed.thinking }] : [],
3526
+ ...streamed.text ? [{ type: "text", text: streamed.text }] : []
3527
+ ],
3354
3528
  stopReason,
3355
3529
  meta: stopReason === "end_turn" ? void 0 : { partial: true }
3356
3530
  };
@@ -4619,6 +4793,7 @@ var init_src4 = __esm({
4619
4793
  #loadConfigProfile;
4620
4794
  #createRuntime;
4621
4795
  #memoryHomeDir;
4796
+ #onSessionListChanged;
4622
4797
  #now;
4623
4798
  #createId;
4624
4799
  #sessions = /* @__PURE__ */ new Map();
@@ -4643,6 +4818,7 @@ var init_src4 = __esm({
4643
4818
  this.#loadConfigProfile = options.loadConfigProfile;
4644
4819
  this.#createRuntime = options.createRuntime;
4645
4820
  this.#memoryHomeDir = options.memoryHomeDir;
4821
+ this.#onSessionListChanged = options.onSessionListChanged;
4646
4822
  this.#now = options.now ?? Date.now;
4647
4823
  this.#createId = options.createId ?? (() => crypto.randomUUID());
4648
4824
  this.#registry = new ProjectRegistry({
@@ -4819,6 +4995,10 @@ var init_src4 = __esm({
4819
4995
  this.#respond(connection, message, await this.#handleUpsertModelProfile(message));
4820
4996
  break;
4821
4997
  }
4998
+ case "remove_model_provider": {
4999
+ this.#respond(connection, message, await this.#handleRemoveModelProvider(message));
5000
+ break;
5001
+ }
4822
5002
  case "fetch_provider_models": {
4823
5003
  this.#respond(connection, message, { models: await this.#fetchProviderModels(message.projectId, message.providerId) });
4824
5004
  break;
@@ -4827,6 +5007,10 @@ var init_src4 = __esm({
4827
5007
  this.#respond(connection, message, { memory: await this.#memorySettingsForProject(message.projectId) });
4828
5008
  break;
4829
5009
  }
5010
+ case "get_memory_status": {
5011
+ this.#respond(connection, message, { status: await this.#memoryStatusForProject(message.projectId) });
5012
+ break;
5013
+ }
4830
5014
  case "upsert_memory_settings": {
4831
5015
  this.#respond(connection, message, { memory: await this.#handleUpsertMemorySettings(message) });
4832
5016
  break;
@@ -4892,6 +5076,9 @@ var init_src4 = __esm({
4892
5076
  workDir: lane.project.workDir,
4893
5077
  model: request.meta.model
4894
5078
  });
5079
+ if (created) {
5080
+ this.#onSessionListChanged?.({ projectId: lane.project.projectId, sessionId });
5081
+ }
4895
5082
  this.#respond(connection, request, { sessionId });
4896
5083
  }
4897
5084
  async #handleLoadSession(connection, request) {
@@ -5332,10 +5519,21 @@ var init_src4 = __esm({
5332
5519
  delta: rawEvent.delta
5333
5520
  });
5334
5521
  break;
5522
+ case "thinking_delta":
5523
+ this.#broadcastTransient(lane.session.header.sessionId, {
5524
+ type: "thinking_delta",
5525
+ sessionId: lane.session.header.sessionId,
5526
+ clientId,
5527
+ ts: this.#now(),
5528
+ eventId: state.assistantEventId,
5529
+ delta: rawEvent.delta
5530
+ });
5531
+ break;
5335
5532
  case "message_end": {
5336
5533
  await this.#appendDiagnostic(lane.session.header.sessionId, "assistant_result", {
5337
5534
  clientId,
5338
5535
  stopReason: rawEvent.message.stopReason,
5536
+ thinkingBlocks: countContentBlocks(rawEvent.message, "thinking"),
5339
5537
  textBlocks: countContentBlocks(rawEvent.message, "text"),
5340
5538
  toolCalls: countContentBlocks(rawEvent.message, "tool_call"),
5341
5539
  inputTokens: rawEvent.message.usage?.inputTokens,
@@ -5531,12 +5729,21 @@ var init_src4 = __esm({
5531
5729
  homeDir: this.#memoryHomeDir,
5532
5730
  now: this.#now,
5533
5731
  onAppend: async (result) => {
5534
- await this.#appendDiagnostic(lane.session.header.sessionId, "memory_daily_appended", {
5535
- clientId,
5536
- path: result.path,
5537
- date: result.date
5538
- });
5539
- await this.#scheduleMemoryDream(lane, clientId);
5732
+ if (result.entry) {
5733
+ await this.#markMemoryDreamDirty(lane, clientId, result.path);
5734
+ }
5735
+ try {
5736
+ await this.#appendDiagnostic(lane.session.header.sessionId, "memory_daily_appended", {
5737
+ clientId,
5738
+ path: result.path,
5739
+ date: result.date,
5740
+ skippedReason: result.skippedReason
5741
+ });
5742
+ } catch {
5743
+ }
5744
+ if (result.entry) {
5745
+ await this.#scheduleMemoryDream(lane, clientId);
5746
+ }
5540
5747
  }
5541
5748
  })
5542
5749
  );
@@ -5767,7 +5974,10 @@ var init_src4 = __esm({
5767
5974
  ...context.senderDisplayName ? [`sender_display_name: ${context.senderDisplayName}`] : [],
5768
5975
  ...context.mentionedBot !== void 0 ? [`mentioned_bot: ${context.mentionedBot}`] : [],
5769
5976
  "",
5770
- "Use SendChannelMessage to reply to the current conversation when needed."
5977
+ "Use SendChannelMessage to reply to the current conversation when needed.",
5978
+ "In IM, send a short acknowledgement before long work so the user does not think the bot is stuck.",
5979
+ "For longer tasks, send concise progress updates instead of waiting until every tool call has finished.",
5980
+ "Keep replies conversational and avoid exposing internal tool names unless they help the user."
5771
5981
  ];
5772
5982
  return this.#appendPersistent(lane, {
5773
5983
  type: "harness_item",
@@ -5808,6 +6018,22 @@ var init_src4 = __esm({
5808
6018
  lastActivityAt: this.#now()
5809
6019
  };
5810
6020
  const delayMs = Math.max(0, memory.dreamIdleMinutes) * 60 * 1e3;
6021
+ const scheduledFor = this.#now() + delayMs;
6022
+ const currentState = await readMemoryDreamState({
6023
+ projectId: lane.project.projectId,
6024
+ homeDir: this.#memoryHomeDir,
6025
+ now: this.#now
6026
+ });
6027
+ await this.#writeMemoryDreamState(lane.project.projectId, {
6028
+ ...currentState ?? {},
6029
+ projectId: String(lane.project.projectId),
6030
+ dirty: true,
6031
+ running: schedule.running,
6032
+ sessionId: String(lane.session.header.sessionId),
6033
+ clientId: String(clientId),
6034
+ lastDailyAppendAt: currentState?.lastDailyAppendAt ?? schedule.lastActivityAt,
6035
+ scheduledFor
6036
+ });
5811
6037
  schedule.timer = setTimeout(() => {
5812
6038
  void this.#runIdleMemoryDream(projectId).catch((cause) => {
5813
6039
  const error = cause instanceof Error ? cause : new Error(String(cause));
@@ -5826,6 +6052,26 @@ var init_src4 = __esm({
5826
6052
  idleMinutes: memory.dreamIdleMinutes
5827
6053
  });
5828
6054
  }
6055
+ async #markMemoryDreamDirty(lane, clientId, dailyPath) {
6056
+ const current = await readMemoryDreamState({
6057
+ projectId: lane.project.projectId,
6058
+ homeDir: this.#memoryHomeDir,
6059
+ now: this.#now
6060
+ });
6061
+ await this.#writeMemoryDreamState(lane.project.projectId, {
6062
+ projectId: String(lane.project.projectId),
6063
+ dirty: true,
6064
+ running: current?.running ?? false,
6065
+ sessionId: String(lane.session.header.sessionId),
6066
+ clientId: String(clientId),
6067
+ lastDailyAppendAt: this.#now(),
6068
+ lastDailyPath: dailyPath,
6069
+ lastFailure: current?.lastFailure,
6070
+ lastSuccessAt: current?.lastSuccessAt,
6071
+ lastProjectMemoryUpdateAt: current?.lastProjectMemoryUpdateAt,
6072
+ lastRootMemoryUpdateAt: current?.lastRootMemoryUpdateAt
6073
+ });
6074
+ }
5829
6075
  async #runIdleMemoryDream(projectId) {
5830
6076
  const schedule = this.#memoryDreams.get(projectId);
5831
6077
  if (!schedule || schedule.running) {
@@ -5834,10 +6080,28 @@ var init_src4 = __esm({
5834
6080
  schedule.running = true;
5835
6081
  schedule.timer = void 0;
5836
6082
  this.#memoryDreams.set(projectId, schedule);
6083
+ const beforeRun = await readMemoryDreamState({
6084
+ projectId,
6085
+ homeDir: this.#memoryHomeDir,
6086
+ now: this.#now
6087
+ });
6088
+ await this.#writeMemoryDreamState(projectId, {
6089
+ ...beforeRun ?? { projectId: String(projectId), dirty: true },
6090
+ projectId: String(projectId),
6091
+ running: true,
6092
+ lastAttemptAt: this.#now()
6093
+ });
5837
6094
  try {
5838
6095
  const lane = await this.#getLane(schedule.sessionId);
5839
6096
  const memory = await this.#safeMemorySettingsForRuntime(lane, schedule.clientId);
5840
6097
  if (!memory.enabled || !memory.autoDream) {
6098
+ await this.#writeMemoryDreamState(projectId, {
6099
+ ...beforeRun ?? { projectId: String(projectId) },
6100
+ projectId: String(projectId),
6101
+ dirty: false,
6102
+ running: false,
6103
+ lastFailure: { at: this.#now(), message: "Memory dream disabled" }
6104
+ });
5841
6105
  return;
5842
6106
  }
5843
6107
  const generated = await this.#generateMemoryUpdate(lane, memory);
@@ -5860,10 +6124,82 @@ var init_src4 = __esm({
5860
6124
  path: paths.rootMemoryPath
5861
6125
  });
5862
6126
  }
6127
+ const now = this.#now();
6128
+ const latestState = await readMemoryDreamState({ projectId, homeDir: this.#memoryHomeDir, now: this.#now });
6129
+ const hasNewDailyDuringRun = latestState?.lastDailyAppendAt !== void 0 && beforeRun?.lastDailyAppendAt !== void 0 && latestState.lastDailyAppendAt > beforeRun.lastDailyAppendAt;
6130
+ await this.#writeMemoryDreamState(projectId, {
6131
+ ...latestState ?? { projectId: String(projectId) },
6132
+ projectId: String(projectId),
6133
+ dirty: hasNewDailyDuringRun,
6134
+ running: false,
6135
+ ...hasNewDailyDuringRun ? {} : { scheduledFor: void 0 },
6136
+ lastSuccessAt: now,
6137
+ lastFailure: void 0,
6138
+ ...generated?.projectMemory?.trim() ? { lastProjectMemoryUpdateAt: now } : {},
6139
+ ...memory.promoteRoot && generated?.rootMemory?.trim() ? { lastRootMemoryUpdateAt: now } : {}
6140
+ });
6141
+ if (hasNewDailyDuringRun) {
6142
+ await this.#scheduleMemoryDream(lane, schedule.clientId);
6143
+ }
6144
+ } catch (cause) {
6145
+ const message = cause instanceof Error ? cause.message : String(cause);
6146
+ await this.#writeMemoryDreamState(projectId, {
6147
+ ...await readMemoryDreamState({ projectId, homeDir: this.#memoryHomeDir, now: this.#now }) ?? { projectId: String(projectId) },
6148
+ projectId: String(projectId),
6149
+ dirty: true,
6150
+ running: false,
6151
+ lastFailure: { at: this.#now(), message }
6152
+ });
6153
+ throw cause;
5863
6154
  } finally {
5864
6155
  this.#memoryDreams.delete(projectId);
5865
6156
  }
5866
6157
  }
6158
+ async #memoryStatusForProject(projectId) {
6159
+ const state = await readMemoryDreamState({
6160
+ projectId,
6161
+ homeDir: this.#memoryHomeDir,
6162
+ now: this.#now
6163
+ });
6164
+ await this.#recoverMemoryDream(projectId, state);
6165
+ const recovered = await readMemoryDreamState({
6166
+ projectId,
6167
+ homeDir: this.#memoryHomeDir,
6168
+ now: this.#now
6169
+ });
6170
+ return {
6171
+ projectId,
6172
+ dirty: recovered?.dirty ?? false,
6173
+ running: recovered?.running ?? false,
6174
+ ...recovered?.lastDailyAppendAt !== void 0 ? { lastDailyAppendAt: recovered.lastDailyAppendAt } : {},
6175
+ ...recovered?.lastDailyPath ? { lastDailyPath: recovered.lastDailyPath } : {},
6176
+ ...recovered?.scheduledFor !== void 0 ? { scheduledFor: recovered.scheduledFor } : {},
6177
+ ...recovered?.lastAttemptAt !== void 0 ? { lastAttemptAt: recovered.lastAttemptAt } : {},
6178
+ ...recovered?.lastSuccessAt !== void 0 ? { lastSuccessAt: recovered.lastSuccessAt } : {},
6179
+ ...recovered?.lastFailure ? { lastFailure: recovered.lastFailure } : {},
6180
+ ...recovered?.lastProjectMemoryUpdateAt !== void 0 ? { lastProjectMemoryUpdateAt: recovered.lastProjectMemoryUpdateAt } : {},
6181
+ ...recovered?.lastRootMemoryUpdateAt !== void 0 ? { lastRootMemoryUpdateAt: recovered.lastRootMemoryUpdateAt } : {}
6182
+ };
6183
+ }
6184
+ async #recoverMemoryDream(projectId, state) {
6185
+ if (!state?.dirty || this.#memoryDreams.has(projectId)) {
6186
+ return;
6187
+ }
6188
+ const lane = [...this.#sessions.values()].find((candidate) => candidate.project.projectId === projectId);
6189
+ if (!lane) {
6190
+ return;
6191
+ }
6192
+ const clientId = state.clientId ? asClientId(state.clientId) : asClientId("client_memory_recovery");
6193
+ await this.#scheduleMemoryDream(lane, clientId);
6194
+ }
6195
+ async #writeMemoryDreamState(projectId, state) {
6196
+ await writeMemoryDreamState({
6197
+ projectId,
6198
+ homeDir: this.#memoryHomeDir,
6199
+ now: this.#now,
6200
+ state
6201
+ });
6202
+ }
5867
6203
  async #generateMemoryUpdate(lane, memory) {
5868
6204
  const selectedModel = await this.#selectedModelFromMeta(
5869
6205
  { projectId: lane.project.projectId, modelSelection: { role: "auxiliary" } },
@@ -6130,13 +6466,17 @@ var init_src4 = __esm({
6130
6466
  if (!extension) {
6131
6467
  throw new Error(`channel_adapter_unavailable: ${current.extensionId}`);
6132
6468
  }
6133
- await extension.adapter.sendMessage(current.target, { text: input.text });
6469
+ await extension.adapter.sendMessage(current.target, {
6470
+ ...input.text ? { text: input.text } : {},
6471
+ ...input.attachments ? { attachments: input.attachments } : {}
6472
+ });
6134
6473
  await this.#appendDiagnostic(lane.session.header.sessionId, "channel_message_sent", {
6135
6474
  extensionId: current.extensionId,
6136
6475
  channel: current.channel,
6137
- externalConversationId: current.externalConversationId
6476
+ externalConversationId: current.externalConversationId,
6477
+ attachments: input.attachments?.length ?? 0
6138
6478
  });
6139
- return { channel: current.channel, target: "current" };
6479
+ return { channel: current.channel, target: "current", attachments: input.attachments?.length ?? 0 };
6140
6480
  }
6141
6481
  })
6142
6482
  );
@@ -6305,6 +6645,7 @@ var init_src4 = __esm({
6305
6645
  externalConversationId,
6306
6646
  projectId: project.projectId
6307
6647
  });
6648
+ this.#onSessionListChanged?.({ projectId: project.projectId, sessionId });
6308
6649
  return binding;
6309
6650
  }
6310
6651
  async #ensureDefaultWorkspaceProject() {
@@ -6427,6 +6768,29 @@ var init_src4 = __esm({
6427
6768
  });
6428
6769
  return this.#listModels(project.projectId);
6429
6770
  }
6771
+ async #handleRemoveModelProvider(request) {
6772
+ const project = await this.#registry.require(request.projectId);
6773
+ const configPath = join10(project.workDir, ".scorel", "config.toml");
6774
+ let existingConfigText;
6775
+ try {
6776
+ existingConfigText = await readFile11(configPath, "utf8");
6777
+ } catch (cause) {
6778
+ if (!isNodeErrorCode4(cause, "ENOENT")) {
6779
+ throw cause;
6780
+ }
6781
+ }
6782
+ await mkdir6(join10(project.workDir, ".scorel"), { recursive: true });
6783
+ await writeFile6(
6784
+ configPath,
6785
+ renderModelProfileConfig({
6786
+ removeProviderId: request.providerId,
6787
+ existingConfigText
6788
+ }),
6789
+ "utf8"
6790
+ );
6791
+ const profile = await this.#listModels(project.projectId);
6792
+ return { ...profile, removed: true };
6793
+ }
6430
6794
  async #memorySettingsForProject(projectId) {
6431
6795
  const config = await this.#configProfileForProject(projectId).catch((cause) => {
6432
6796
  if (isMissingConfigError(cause)) {