mulmoclaude 2.7.0 → 2.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.
Files changed (77) hide show
  1. package/client/assets/Graph-ChAxkn9o.js +1 -0
  2. package/client/assets/dist-Bij44LdC.js +2 -0
  3. package/client/assets/echarts-GV3U9sVf.js +1 -0
  4. package/client/assets/echarts-gl-BXx4On0Y.js +4282 -0
  5. package/client/assets/echarts-wordcloud-_9uQUAfp.js +1 -0
  6. package/client/assets/emscripten-module-Q67P5WYC-BU3Hlttz.js +2160 -0
  7. package/client/assets/ffi-4V2b7xu3.js +1 -0
  8. package/client/assets/{index-lkpgbKtk.js → index-D6xzCZhP.js} +600 -589
  9. package/client/assets/index-DzMsXDnj.css +2 -0
  10. package/client/assets/{install-wxcddUsO.js → install-C1moNcrn.js} +1 -1
  11. package/client/assets/{marp-DcTywifN.js → marp-Du5Sym5b.js} +1 -1
  12. package/client/assets/module-ES6BEMUI-CDTIpMiA.js +6 -0
  13. package/client/index.html +4 -3
  14. package/package.json +5 -4
  15. package/server/api/routes/plugins.ts +8 -59
  16. package/server/build/mcp-server.mjs +609 -608
  17. package/server/index.ts +1 -0
  18. package/server/plugins/confined-file-ops.ts +136 -0
  19. package/server/plugins/runtime.ts +8 -115
  20. package/server/plugins/spreadsheet-builtin.ts +26 -0
  21. package/server/plugins/spreadsheet-save-outcome.ts +7 -0
  22. package/server/utils/files/store-resolvers.ts +1 -1
  23. package/src/lang/de.ts +0 -20
  24. package/src/lang/en.ts +0 -20
  25. package/src/lang/es.ts +0 -20
  26. package/src/lang/fr.ts +0 -20
  27. package/src/lang/ja.ts +0 -20
  28. package/src/lang/ko.ts +0 -20
  29. package/src/lang/pt-BR.ts +0 -20
  30. package/src/lang/zh.ts +0 -20
  31. package/src/plugins/spreadsheet/definition.ts +9 -127
  32. package/src/plugins/spreadsheet/index.ts +7 -4
  33. package/src/plugins/spreadsheet/meta.ts +2 -4
  34. package/client/assets/echarts-gl-Cq76Z6DA.js +0 -4282
  35. package/client/assets/index-Cz7JLE0k.css +0 -2
  36. package/server/utils/files/spreadsheet-store.ts +0 -26
  37. package/src/plugins/spreadsheet/Preview.vue +0 -31
  38. package/src/plugins/spreadsheet/View.vue +0 -1007
  39. package/src/plugins/spreadsheet/cellHighlights.ts +0 -78
  40. package/src/plugins/spreadsheet/engine/calculator.ts +0 -427
  41. package/src/plugins/spreadsheet/engine/cellBuilder.ts +0 -89
  42. package/src/plugins/spreadsheet/engine/cellEmpty.ts +0 -26
  43. package/src/plugins/spreadsheet/engine/cellFormatting.ts +0 -59
  44. package/src/plugins/spreadsheet/engine/coerce-boolean.ts +0 -26
  45. package/src/plugins/spreadsheet/engine/condition.ts +0 -182
  46. package/src/plugins/spreadsheet/engine/date-locale.ts +0 -47
  47. package/src/plugins/spreadsheet/engine/date-parser.ts +0 -234
  48. package/src/plugins/spreadsheet/engine/date-utils.ts +0 -73
  49. package/src/plugins/spreadsheet/engine/datedif.ts +0 -93
  50. package/src/plugins/spreadsheet/engine/engine.ts +0 -177
  51. package/src/plugins/spreadsheet/engine/evaluator.ts +0 -494
  52. package/src/plugins/spreadsheet/engine/financial-math.ts +0 -114
  53. package/src/plugins/spreadsheet/engine/formatter.ts +0 -158
  54. package/src/plugins/spreadsheet/engine/formulaError.ts +0 -67
  55. package/src/plugins/spreadsheet/engine/formulaRefs.ts +0 -202
  56. package/src/plugins/spreadsheet/engine/functions/date.ts +0 -212
  57. package/src/plugins/spreadsheet/engine/functions/financial.ts +0 -244
  58. package/src/plugins/spreadsheet/engine/functions/index.ts +0 -16
  59. package/src/plugins/spreadsheet/engine/functions/logical.ts +0 -221
  60. package/src/plugins/spreadsheet/engine/functions/lookup-math.ts +0 -28
  61. package/src/plugins/spreadsheet/engine/functions/lookup.ts +0 -257
  62. package/src/plugins/spreadsheet/engine/functions/mathematical.ts +0 -283
  63. package/src/plugins/spreadsheet/engine/functions/statistical-math.ts +0 -91
  64. package/src/plugins/spreadsheet/engine/functions/statistical.ts +0 -290
  65. package/src/plugins/spreadsheet/engine/functions/text.ts +0 -398
  66. package/src/plugins/spreadsheet/engine/index.ts +0 -34
  67. package/src/plugins/spreadsheet/engine/jsonCellLocator.ts +0 -111
  68. package/src/plugins/spreadsheet/engine/math-ops.ts +0 -98
  69. package/src/plugins/spreadsheet/engine/numericCoercion.ts +0 -61
  70. package/src/plugins/spreadsheet/engine/parser.ts +0 -40
  71. package/src/plugins/spreadsheet/engine/registry.ts +0 -201
  72. package/src/plugins/spreadsheet/engine/responseDecoder.ts +0 -68
  73. package/src/plugins/spreadsheet/engine/spreadsheet-errors.ts +0 -82
  74. package/src/plugins/spreadsheet/engine/textFormat.ts +0 -108
  75. package/src/plugins/spreadsheet/engine/translateFormula.ts +0 -69
  76. package/src/plugins/spreadsheet/engine/types.ts +0 -81
  77. package/src/plugins/spreadsheet/keyboardNav.ts +0 -38
package/server/index.ts CHANGED
@@ -55,6 +55,7 @@ import "./plugins/markdown-builtin.js";
55
55
  // Side-effect: registers the built-in "html" dispatch handler so the
56
56
  // presentHtml View's useRuntime().dispatch({ kind }) resolves (phase 2).
57
57
  import "./plugins/html-builtin.js";
58
+ import "./plugins/spreadsheet-builtin.js";
58
59
  // Side-effect: registers the built-in "shapescript" dispatch handler so the
59
60
  // presentShapeScript View's useRuntime().dispatch({ kind }) resolves — the
60
61
  // source editor's load / save against artifacts/shapes/.
@@ -0,0 +1,136 @@
1
+ // Scoped FileOps: a lexical check on the plugin-supplied path, then a realpath check so a
2
+ // symlink inside the scope root cannot lead out of it. Check-then-use, like `resolveWithinRoot`: not atomic
3
+ // against a path swapped between the check and the call (Node fs has no openat / no-follow-parent).
4
+
5
+ import path from "node:path";
6
+ import { mkdir, readFile, readdir, realpath, stat as fsStat, unlink as fsUnlink } from "node:fs/promises";
7
+ import type { FileOps } from "gui-chat-protocol";
8
+ import { writeFileAtomic } from "../utils/files/atomic.js";
9
+ import { isEnoent, resolveWriteWithinRoot } from "../utils/files/safe.js";
10
+ import { ensureInsideBase } from "./runtime-loader.js";
11
+
12
+ // ─────────────────────────────────────────────────────────────────────
13
+ // Path normalisation contract (see plans/done/feat-plugin-runtime-extensions-1110.md)
14
+ //
15
+ // 1. Replace `\` with `/` (Windows path.join leak repair).
16
+ // 2. `path.posix.normalize` (folds `..`, `.`, `//`).
17
+ // 3. Reject if the normalised form starts with `..` or `/`
18
+ // (would escape the scope root or be absolute).
19
+ // 4. `path.join(scopeRoot, ...segments)` — platform-aware join so
20
+ // the returned absolute path uses the host OS separator
21
+ // (backslashes on Windows, forward slashes on POSIX). Critical
22
+ // for `ensureInsideBase` to accept the result.
23
+ //
24
+ // Plugin authors should never need `node:path`; the platform meets
25
+ // them where they are even if they mis-import it on Windows.
26
+ // ─────────────────────────────────────────────────────────────────────
27
+
28
+ /** Resolve a plugin-supplied path to an absolute path inside the
29
+ * plugin's scope root. Throws on traversal. Lexical only — symlinks
30
+ * are checked by the FileOps below. Exported for tests. */
31
+ export function normalizePluginPath(scopeRoot: string, rel: string): string {
32
+ const slashed = rel.replace(/\\/g, "/");
33
+ const normalised = path.posix.normalize(slashed);
34
+ // `path.posix.normalize` produces `..` alone if the input is `..` itself, hence the equality branch.
35
+ if (normalised === ".." || normalised.startsWith("../") || normalised.startsWith("/")) {
36
+ throw new Error(`path escapes plugin scope: ${rel}`);
37
+ }
38
+ const segments = normalised === "." ? [] : normalised.split("/");
39
+ const absolute = path.join(scopeRoot, ...segments);
40
+ // Defence in depth so a future change to the lexical step cannot silently regress.
41
+ if (!ensureInsideBase(absolute, scopeRoot)) {
42
+ throw new Error(`path escapes plugin scope: ${rel}`);
43
+ }
44
+ return absolute;
45
+ }
46
+
47
+ /** The lexically checked path, relative to the scope root in host-native form ("" for the root). */
48
+ const scopeRelative = (scopeRoot: string, rel: string): string => path.relative(scopeRoot, normalizePluginPath(scopeRoot, rel));
49
+
50
+ /** An escape reads as a missing file, so callers that treat ENOENT as "not there" stay closed. */
51
+ const notInScope = (rel: string): Error & { code: string } => Object.assign(new Error(`not inside the scope: ${rel}`), { code: "ENOENT" });
52
+
53
+ /** Only a missing path reads as null; EACCES, ELOOP, ENOTDIR … propagate as before. */
54
+ const realpathIfPresent = async (absPath: string): Promise<string | null> => {
55
+ try {
56
+ return await realpath(absPath);
57
+ } catch (err) {
58
+ if (isEnoent(err)) return null;
59
+ throw err;
60
+ }
61
+ };
62
+
63
+ const isWithin = (rootReal: string, candidateReal: string): boolean => candidateReal === rootReal || candidateReal.startsWith(rootReal + path.sep);
64
+
65
+ /** Real location of `relative` (host-native, from `scopeRelative`) if it exists inside the root; null when missing or outside. */
66
+ const resolveExisting = async (rootReal: string, relative: string): Promise<string | null> => {
67
+ const candidateReal = await realpathIfPresent(path.resolve(rootReal, relative));
68
+ return candidateReal !== null && isWithin(rootReal, candidateReal) ? candidateReal : null;
69
+ };
70
+
71
+ // Only a write creates the scope root; a read-only call against an absent root sees "missing".
72
+ const realRoot = async (scopeRoot: string): Promise<string> => {
73
+ await mkdir(scopeRoot, { recursive: true });
74
+ return realpath(scopeRoot);
75
+ };
76
+
77
+ const existingPath = async (scopeRoot: string, rel: string): Promise<string | null> => {
78
+ const relative = scopeRelative(scopeRoot, rel);
79
+ const rootReal = await realpathIfPresent(scopeRoot);
80
+ return rootReal === null ? null : resolveExisting(rootReal, relative);
81
+ };
82
+
83
+ const requireExisting = async (scopeRoot: string, rel: string): Promise<string> => {
84
+ const resolved = await existingPath(scopeRoot, rel);
85
+ if (resolved === null) throw notInScope(rel);
86
+ return resolved;
87
+ };
88
+
89
+ const writeTarget = async (scopeRoot: string, rel: string): Promise<string> => {
90
+ const target = await resolveWriteWithinRoot(await realRoot(scopeRoot), scopeRelative(scopeRoot, rel));
91
+ if (target === null) throw new Error(`refusing to write outside the scope: ${rel}`);
92
+ return target;
93
+ };
94
+
95
+ // unlink does not follow the leaf, so only the parent is confined: a link is removed, never its target.
96
+ const unlinkTarget = async (scopeRoot: string, rel: string): Promise<string | null> => {
97
+ const relative = scopeRelative(scopeRoot, rel);
98
+ const rootReal = await realpathIfPresent(scopeRoot);
99
+ if (rootReal === null) return null;
100
+ if (relative === "") return rootReal;
101
+ const parentReal = await resolveExisting(rootReal, path.dirname(relative));
102
+ return parentReal === null ? null : path.join(parentReal, path.basename(relative));
103
+ };
104
+
105
+ const ignoreEnoent = async <T>(operation: () => Promise<T>, fallback: T): Promise<T> => {
106
+ try {
107
+ return await operation();
108
+ } catch (err) {
109
+ if (isEnoent(err)) return fallback;
110
+ throw err;
111
+ }
112
+ };
113
+
114
+ export function makeConfinedFileOps(scopeRoot: string): FileOps {
115
+ return {
116
+ read: async (rel) => readFile(await requireExisting(scopeRoot, rel), "utf-8"),
117
+ readBytes: async (rel) => {
118
+ const buf = await readFile(await requireExisting(scopeRoot, rel));
119
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
120
+ },
121
+ write: async (rel, content) => writeFileAtomic(await writeTarget(scopeRoot, rel), content),
122
+ readDir: async (rel) => {
123
+ const resolved = await existingPath(scopeRoot, rel);
124
+ return resolved === null ? [] : ignoreEnoent(() => readdir(resolved), []);
125
+ },
126
+ stat: async (rel) => {
127
+ const { mtimeMs, size } = await fsStat(await requireExisting(scopeRoot, rel));
128
+ return { mtimeMs, size };
129
+ },
130
+ exists: async (rel) => (await existingPath(scopeRoot, rel)) !== null,
131
+ unlink: async (rel) => {
132
+ const target = await unlinkTarget(scopeRoot, rel);
133
+ if (target !== null) await ignoreEnoent(() => fsUnlink(target), undefined);
134
+ },
135
+ };
136
+ }
@@ -3,25 +3,23 @@
3
3
  //
4
4
  // Every helper closes over `pkgName` so a plugin's pubsub channel,
5
5
  // data dir, log prefix etc. cannot leak across plugins. Path arguments
6
- // to `files.{data,config}` are normalised to POSIX, then anchored
7
- // inside the plugin's scope root via `ensureInsideBase` — so misuse of
8
- // `node:path` on Windows still works and `"../../etc/passwd"` is
9
- // rejected.
6
+ // to `files.{data,config,artifacts}` are confined to their scope root
7
+ // by `makeConfinedFileOps` — lexically, then by realpath, so neither
8
+ // `"../../etc/passwd"` nor a symlink inside the root leads out.
10
9
  //
11
10
  // This module is server-side only. The browser-side counterpart lives
12
11
  // at `src/utils/plugin/runtime.ts`.
13
12
 
14
13
  import path from "node:path";
15
- import { readFile, readdir, stat as fsStat, unlink as fsUnlink } from "node:fs/promises";
16
14
  import { randomUUID } from "node:crypto";
17
15
  import type { FileOps, PluginFetchJsonOptions, PluginFetchOptions, PluginRuntime } from "gui-chat-protocol";
18
16
 
19
17
  import { WORKSPACE_PATHS, workspacePath } from "../workspace/paths.js";
20
18
  import { writeFileAtomic } from "../utils/files/atomic.js";
21
19
  import { errorMessage } from "../utils/errors.js";
22
- import { isErrorWithCode, isRecord } from "../utils/types.js";
20
+ import { isRecord } from "../utils/types.js";
23
21
  import { log as hostLog, type Logger } from "../system/logger/index.js";
24
- import { ensureInsideBase } from "./runtime-loader.js";
22
+ import { makeConfinedFileOps } from "./confined-file-ops.js";
25
23
  import { ONE_SECOND_MS } from "../utils/time.js";
26
24
  import type { IPubSub } from "../events/pub-sub/index.js";
27
25
  import * as notifierEngine from "../notifier/engine.js";
@@ -35,59 +33,6 @@ import { BUILTIN_ROLE_IDS } from "../../src/config/roles.js";
35
33
 
36
34
  const DEFAULT_FETCH_TIMEOUT_MS = 10 * ONE_SECOND_MS;
37
35
 
38
- // ─────────────────────────────────────────────────────────────────────
39
- // Path normalisation contract (see plans/done/feat-plugin-runtime-extensions-1110.md)
40
- //
41
- // 1. Replace `\` with `/` (Windows path.join leak repair).
42
- // 2. `path.posix.normalize` (folds `..`, `.`, `//`).
43
- // 3. Reject if the normalised form starts with `..` or `/`
44
- // (would escape the scope root or be absolute).
45
- // 4. `path.join(scopeRoot, ...segments)` — platform-aware join so
46
- // the returned absolute path uses the host OS separator
47
- // (backslashes on Windows, forward slashes on POSIX). Critical
48
- // for `ensureInsideBase` to accept the result.
49
- //
50
- // Plugin authors should never need `node:path`; the platform meets
51
- // them where they are even if they mis-import it on Windows.
52
- // ─────────────────────────────────────────────────────────────────────
53
-
54
- /** Resolve a plugin-supplied path to an absolute path inside the
55
- * plugin's scope root. Throws on traversal. Exported for tests.
56
- *
57
- * Cross-platform: `scopeRoot` is the host OS's native form (drive
58
- * letter + backslashes on Windows, forward slashes on POSIX). The
59
- * user-supplied `rel` is POSIX-relative by contract; we sanitise
60
- * for the lexical traversal check via `path.posix.*`, then join
61
- * with `path.join` so the returned absolute path is in the host's
62
- * native form (which `ensureInsideBase` requires). */
63
- export function normalizePluginPath(scopeRoot: string, rel: string): string {
64
- // Defang Windows path.join output and any mixed-separator inputs.
65
- const slashed = rel.replace(/\\/g, "/");
66
- // Fold `..` / `.` / repeated `/` lexically (no fs touch yet).
67
- const normalised = path.posix.normalize(slashed);
68
- // Reject lexically — anything that escapes the scope root after
69
- // normalisation either starts with `..` (parent-traversal) or is
70
- // absolute (`/etc/passwd`). `path.posix.normalize` produces `..`
71
- // alone if the input is `..` itself, hence the equality branch.
72
- if (normalised === ".." || normalised.startsWith("../") || normalised.startsWith("/")) {
73
- throw new Error(`path escapes plugin scope: ${rel}`);
74
- }
75
- const segments = normalised === "." ? [] : normalised.split("/");
76
- // `path.join` (platform-aware) so the result uses the host
77
- // separator. On Windows that's backslashes; on POSIX, forward
78
- // slashes — matching `scopeRoot`'s existing form so
79
- // `ensureInsideBase`'s `path.resolve` + `path.sep` comparison
80
- // works across both platforms.
81
- const absolute = path.join(scopeRoot, ...segments);
82
- // Defence in depth: even though the lexical check above should
83
- // catch every escape, run `ensureInsideBase` so a future change to
84
- // either step doesn't silently regress the safety guarantee.
85
- if (!ensureInsideBase(absolute, scopeRoot)) {
86
- throw new Error(`path escapes plugin scope: ${rel}`);
87
- }
88
- return absolute;
89
- }
90
-
91
36
  // ─────────────────────────────────────────────────────────────────────
92
37
  // Scoped FileOps factory
93
38
  // ─────────────────────────────────────────────────────────────────────
@@ -99,59 +44,7 @@ export function normalizePluginPath(scopeRoot: string, rel: string): string {
99
44
  * category subdir (`charts/`, …) and all write logic; the host only
100
45
  * provides this generic primitive. */
101
46
  export function makeArtifactsFileOps(): FileOps {
102
- return makeFileOps(path.join(workspacePath, "artifacts"));
103
- }
104
-
105
- function makeFileOps(scopeRoot: string): FileOps {
106
- // The scope root may not exist on first use. Lazy-create at write
107
- // time (writeFileAtomic does its own mkdir for the file's parent;
108
- // we ensure the scope root exists for readDir() / stat() etc.).
109
- // Reads against a never-written plugin throw ENOENT — exposed as
110
- // `exists() === false` rather than handled here.
111
-
112
- return {
113
- async read(rel) {
114
- return readFile(normalizePluginPath(scopeRoot, rel), "utf-8");
115
- },
116
- async readBytes(rel) {
117
- const buf = await readFile(normalizePluginPath(scopeRoot, rel));
118
- return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
119
- },
120
- async write(rel, content) {
121
- const abs = normalizePluginPath(scopeRoot, rel);
122
- await writeFileAtomic(abs, content);
123
- },
124
- async readDir(rel) {
125
- const abs = normalizePluginPath(scopeRoot, rel);
126
- try {
127
- return await readdir(abs);
128
- } catch (err) {
129
- if (isErrorWithCode(err) && err.code === "ENOENT") return [];
130
- throw err;
131
- }
132
- },
133
- async stat(rel) {
134
- const { mtimeMs, size } = await fsStat(normalizePluginPath(scopeRoot, rel));
135
- return { mtimeMs, size };
136
- },
137
- async exists(rel) {
138
- try {
139
- await fsStat(normalizePluginPath(scopeRoot, rel));
140
- return true;
141
- } catch (err) {
142
- if (isErrorWithCode(err) && err.code === "ENOENT") return false;
143
- throw err;
144
- }
145
- },
146
- async unlink(rel) {
147
- try {
148
- await fsUnlink(normalizePluginPath(scopeRoot, rel));
149
- } catch (err) {
150
- if (isErrorWithCode(err) && err.code === "ENOENT") return;
151
- throw err;
152
- }
153
- },
154
- };
47
+ return makeConfinedFileOps(path.join(workspacePath, "artifacts"));
155
48
  }
156
49
 
157
50
  // ─────────────────────────────────────────────────────────────────────
@@ -395,8 +288,8 @@ export function makePluginRuntime(deps: MakePluginRuntimeDeps): MulmoclaudeRunti
395
288
  pubsub: makeScopedPubSub(pkgName, pubsub),
396
289
  locale,
397
290
  files: {
398
- data: makeFileOps(dataRoot),
399
- config: makeFileOps(configRoot),
291
+ data: makeConfinedFileOps(dataRoot),
292
+ config: makeConfinedFileOps(configRoot),
400
293
  artifacts: makeArtifactsFileOps(),
401
294
  },
402
295
  log: makeScopedLogger(pkgName),
@@ -0,0 +1,26 @@
1
+ // MulmoClaude's host wiring for the presentSpreadsheet View's dispatch channel.
2
+ // The @gui-chat-plugin/spreadsheet View loads and saves its workbook through
3
+ // `useRuntime().dispatch({ kind: "loadSpreadsheet" | "saveSpreadsheet", … })`;
4
+ // this routes those calls to the package's `executeSpreadsheetDispatch` against
5
+ // the workspace `artifacts/` (confined by realpath, so a symlink cannot lead out), then publishes a file-change event
6
+ // after a save that wrote the file. Imported for side effect at boot (server/index.ts).
7
+
8
+ import { executeSpreadsheetDispatch, isSpreadsheetDispatchArgs } from "@gui-chat-plugin/spreadsheet";
9
+ import { makeArtifactsFileOps } from "./runtime.js";
10
+ import { publishFileChange } from "../events/file-change.js";
11
+ import { describeKind, registerBuiltinDispatch } from "./builtin-dispatch.js";
12
+ import { wroteSpreadsheetFile } from "./spreadsheet-save-outcome.js";
13
+
14
+ /** Matches `wrapWithScope("spreadsheet", …)` in `src/plugins/spreadsheet/index.ts`. */
15
+ const SPREADSHEET_SCOPE = "spreadsheet";
16
+
17
+ registerBuiltinDispatch(SPREADSHEET_SCOPE, async (args) => {
18
+ if (!isSpreadsheetDispatchArgs(args)) {
19
+ throw new Error(`spreadsheet plugin: unrecognised dispatch payload (kind=${describeKind(args)})`);
20
+ }
21
+ const result = await executeSpreadsheetDispatch(makeArtifactsFileOps(), args);
22
+ if (wroteSpreadsheetFile(args, result.data)) {
23
+ void publishFileChange(args.path);
24
+ }
25
+ return result;
26
+ });
@@ -0,0 +1,7 @@
1
+ // Whether a spreadsheet dispatch changed the file on disk, so the host should announce it.
2
+ // A save refused for a conflict (the file moved on since the View's baseVersion) wrote nothing.
3
+
4
+ import type { SpreadsheetDispatchArgs, SpreadsheetFileData } from "@gui-chat-plugin/spreadsheet";
5
+
6
+ export const wroteSpreadsheetFile = (args: SpreadsheetDispatchArgs, data: SpreadsheetFileData | undefined): boolean =>
7
+ args.kind === "saveSpreadsheet" && data !== undefined && data.conflict !== true;
@@ -1,5 +1,5 @@
1
1
  // Shared resolver factory for file-store modules (attachment-store,
2
- // image-store, spreadsheet-store). Each store used to define its own
2
+ // image-store). Each store used to define its own
3
3
  // `safeResolve(relativePath)` wrapper of identical shape: ensure the
4
4
  // root dir exists, cache its realpath, strip the leading store
5
5
  // prefix, hand off to `resolveWithinRoot`. That left three name-cloned
package/src/lang/de.ts CHANGED
@@ -1099,26 +1099,6 @@ const deMessages = {
1099
1099
  pluginSkill: {
1100
1100
  noDescription: "(keine Beschreibung)",
1101
1101
  },
1102
- pluginSpreadsheet: {
1103
- previewUntitled: "Tabelle",
1104
- previewSheets: "{count} Blatt | {count} Blätter",
1105
- untitled: "Tabelle",
1106
- excel: "Excel",
1107
- valuePlaceholder: "Wert",
1108
- valueOrFormulaPlaceholder: "Wert oder Formel (z. B. 100 oder SUM(B2:B11))",
1109
- formatPlaceholder: "Format (z. B. $#,##0.00)",
1110
- loading: "Tabelle wird geladen...",
1111
- noData: "Keine Tabellendaten verfügbar",
1112
- editData: "Tabellendaten bearbeiten",
1113
- applyChanges: "Änderungen übernehmen",
1114
- dataMustBeArray: "Daten müssen ein Array von Blättern sein",
1115
- loadFailed: "Laden der Tabelle fehlgeschlagen: {error}",
1116
- invalidJsonAlert: "Ungültiges JSON-Format: {error}",
1117
- unknownError: "Unbekannter Fehler",
1118
- update: "Aktualisieren",
1119
- stringType: "Zeichenkette",
1120
- formulaType: "Formel",
1121
- },
1122
1102
  app: {
1123
1103
  startConversation: "Gespräch beginnen",
1124
1104
  thinking: "Denkt nach…",
package/src/lang/en.ts CHANGED
@@ -1109,26 +1109,6 @@ const enMessages = {
1109
1109
  pluginSkill: {
1110
1110
  noDescription: "(no description)",
1111
1111
  },
1112
- pluginSpreadsheet: {
1113
- previewUntitled: "Spreadsheet",
1114
- previewSheets: "{count} sheet | {count} sheets",
1115
- untitled: "Spreadsheet",
1116
- excel: "Excel",
1117
- valuePlaceholder: "Value",
1118
- valueOrFormulaPlaceholder: "Value or Formula (e.g., 100 or SUM(B2:B11))",
1119
- formatPlaceholder: "Format (e.g., $#,##0.00)",
1120
- loading: "Loading spreadsheet...",
1121
- noData: "No spreadsheet data available",
1122
- editData: "Edit Spreadsheet Data",
1123
- applyChanges: "Apply Changes",
1124
- dataMustBeArray: "Data must be an array of sheets",
1125
- loadFailed: "Failed to load spreadsheet: {error}",
1126
- invalidJsonAlert: "Invalid JSON format: {error}",
1127
- unknownError: "Unknown error",
1128
- update: "Update",
1129
- stringType: "String",
1130
- formulaType: "Formula",
1131
- },
1132
1112
  app: {
1133
1113
  startConversation: "Start a conversation",
1134
1114
  thinking: "Thinking…",
package/src/lang/es.ts CHANGED
@@ -1097,26 +1097,6 @@ const esMessages = {
1097
1097
  pluginSkill: {
1098
1098
  noDescription: "(sin descripción)",
1099
1099
  },
1100
- pluginSpreadsheet: {
1101
- previewUntitled: "Hoja de cálculo",
1102
- previewSheets: "{count} hoja | {count} hojas",
1103
- untitled: "Hoja de cálculo",
1104
- excel: "Excel",
1105
- valuePlaceholder: "Valor",
1106
- valueOrFormulaPlaceholder: "Valor o Fórmula (ej. 100 o SUM(B2:B11))",
1107
- formatPlaceholder: "Formato (ej. $#,##0.00)",
1108
- loading: "Cargando la hoja de cálculo...",
1109
- noData: "No hay datos de hoja de cálculo disponibles",
1110
- editData: "Editar datos de la hoja de cálculo",
1111
- applyChanges: "Aplicar cambios",
1112
- dataMustBeArray: "Los datos deben ser una matriz de hojas",
1113
- loadFailed: "Error al cargar la hoja de cálculo: {error}",
1114
- invalidJsonAlert: "Formato JSON no válido: {error}",
1115
- unknownError: "Error desconocido",
1116
- update: "Actualizar",
1117
- stringType: "Texto",
1118
- formulaType: "Fórmula",
1119
- },
1120
1100
  app: {
1121
1101
  startConversation: "Inicia una conversación",
1122
1102
  thinking: "Pensando…",
package/src/lang/fr.ts CHANGED
@@ -1089,26 +1089,6 @@ const frMessages = {
1089
1089
  pluginSkill: {
1090
1090
  noDescription: "(aucune description)",
1091
1091
  },
1092
- pluginSpreadsheet: {
1093
- previewUntitled: "Tableur",
1094
- previewSheets: "{count} feuille | {count} feuilles",
1095
- untitled: "Tableur",
1096
- excel: "Excel",
1097
- valuePlaceholder: "Valeur",
1098
- valueOrFormulaPlaceholder: "Valeur ou formule (ex. 100 ou SUM(B2:B11))",
1099
- formatPlaceholder: "Format (ex. $#,##0.00)",
1100
- loading: "Chargement du tableur...",
1101
- noData: "Aucune donnée de tableur disponible",
1102
- editData: "Modifier les données du tableur",
1103
- applyChanges: "Appliquer les modifications",
1104
- dataMustBeArray: "Les données doivent être un tableau de feuilles",
1105
- loadFailed: "Échec du chargement du tableur : {error}",
1106
- invalidJsonAlert: "Format JSON non valide : {error}",
1107
- unknownError: "Erreur inconnue",
1108
- update: "Mettre à jour",
1109
- stringType: "Texte",
1110
- formulaType: "Formule",
1111
- },
1112
1092
  app: {
1113
1093
  startConversation: "Démarrer une conversation",
1114
1094
  thinking: "Réflexion…",
package/src/lang/ja.ts CHANGED
@@ -1083,26 +1083,6 @@ const jaMessages = {
1083
1083
  pluginSkill: {
1084
1084
  noDescription: "(説明なし)",
1085
1085
  },
1086
- pluginSpreadsheet: {
1087
- previewUntitled: "スプレッドシート",
1088
- previewSheets: "{count} シート",
1089
- untitled: "スプレッドシート",
1090
- excel: "Excel",
1091
- valuePlaceholder: "値",
1092
- valueOrFormulaPlaceholder: "値または数式(例: 100, SUM(B2:B11))",
1093
- formatPlaceholder: "書式(例: $#,##0.00)",
1094
- loading: "スプレッドシート読み込み中...",
1095
- noData: "スプレッドシートのデータがありません",
1096
- editData: "スプレッドシートデータを編集",
1097
- applyChanges: "変更を適用",
1098
- dataMustBeArray: "データはシートの配列である必要があります",
1099
- loadFailed: "スプレッドシートの読み込みに失敗: {error}",
1100
- invalidJsonAlert: "不正な JSON 形式です: {error}",
1101
- unknownError: "不明なエラー",
1102
- update: "更新",
1103
- stringType: "文字列",
1104
- formulaType: "数式",
1105
- },
1106
1086
  app: {
1107
1087
  startConversation: "会話を開始してください",
1108
1088
  thinking: "考え中…",
package/src/lang/ko.ts CHANGED
@@ -1081,26 +1081,6 @@ const koMessages = {
1081
1081
  pluginSkill: {
1082
1082
  noDescription: "(설명 없음)",
1083
1083
  },
1084
- pluginSpreadsheet: {
1085
- previewUntitled: "스프레드시트",
1086
- previewSheets: "{count}개 시트",
1087
- untitled: "스프레드시트",
1088
- excel: "Excel",
1089
- valuePlaceholder: "값",
1090
- valueOrFormulaPlaceholder: "값 또는 수식 (예: 100 또는 SUM(B2:B11))",
1091
- formatPlaceholder: "형식 (예: $#,##0.00)",
1092
- loading: "스프레드시트를 불러오는 중...",
1093
- noData: "사용 가능한 스프레드시트 데이터가 없습니다",
1094
- editData: "스프레드시트 데이터 편집",
1095
- applyChanges: "변경 사항 적용",
1096
- dataMustBeArray: "데이터는 시트 배열이어야 합니다",
1097
- loadFailed: "스프레드시트 불러오기 실패: {error}",
1098
- invalidJsonAlert: "잘못된 JSON 형식: {error}",
1099
- unknownError: "알 수 없는 오류",
1100
- update: "업데이트",
1101
- stringType: "문자열",
1102
- formulaType: "수식",
1103
- },
1104
1084
  app: {
1105
1085
  startConversation: "대화 시작",
1106
1086
  thinking: "생각 중…",
package/src/lang/pt-BR.ts CHANGED
@@ -1085,26 +1085,6 @@ const ptBRMessages = {
1085
1085
  pluginSkill: {
1086
1086
  noDescription: "(sem descrição)",
1087
1087
  },
1088
- pluginSpreadsheet: {
1089
- previewUntitled: "Planilha",
1090
- previewSheets: "{count} aba | {count} abas",
1091
- untitled: "Planilha",
1092
- excel: "Excel",
1093
- valuePlaceholder: "Valor",
1094
- valueOrFormulaPlaceholder: "Valor ou Fórmula (ex.: 100 ou SUM(B2:B11))",
1095
- formatPlaceholder: "Formato (ex.: $#,##0.00)",
1096
- loading: "Carregando planilha...",
1097
- noData: "Nenhum dado de planilha disponível",
1098
- editData: "Editar dados da planilha",
1099
- applyChanges: "Aplicar alterações",
1100
- dataMustBeArray: "Os dados devem ser uma matriz de planilhas",
1101
- loadFailed: "Falha ao carregar a planilha: {error}",
1102
- invalidJsonAlert: "Formato JSON inválido: {error}",
1103
- unknownError: "Erro desconhecido",
1104
- update: "Atualizar",
1105
- stringType: "Texto",
1106
- formulaType: "Fórmula",
1107
- },
1108
1088
  app: {
1109
1089
  startConversation: "Iniciar uma conversa",
1110
1090
  thinking: "Pensando…",
package/src/lang/zh.ts CHANGED
@@ -1065,26 +1065,6 @@ const zhMessages = {
1065
1065
  pluginSkill: {
1066
1066
  noDescription: "(无描述)",
1067
1067
  },
1068
- pluginSpreadsheet: {
1069
- previewUntitled: "电子表格",
1070
- previewSheets: "{count} 个工作表",
1071
- untitled: "电子表格",
1072
- excel: "Excel",
1073
- valuePlaceholder: "值",
1074
- valueOrFormulaPlaceholder: "值或公式(例如 100 或 SUM(B2:B11))",
1075
- formatPlaceholder: "格式(例如 $#,##0.00)",
1076
- loading: "正在加载电子表格...",
1077
- noData: "没有可用的电子表格数据",
1078
- editData: "编辑电子表格数据",
1079
- applyChanges: "应用更改",
1080
- dataMustBeArray: "数据必须是工作表数组",
1081
- loadFailed: "加载电子表格失败: {error}",
1082
- invalidJsonAlert: "无效的 JSON 格式: {error}",
1083
- unknownError: "未知错误",
1084
- update: "更新",
1085
- stringType: "字符串",
1086
- formulaType: "公式",
1087
- },
1088
1068
  app: {
1089
1069
  startConversation: "开始对话",
1090
1070
  thinking: "思考中…",