@sunnoy/wecom 2.1.0 → 2.2.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 (43) hide show
  1. package/README.md +6 -2
  2. package/index.js +2 -0
  3. package/openclaw.plugin.json +3 -0
  4. package/package.json +5 -3
  5. package/skills/wecom-contact-lookup/SKILL.md +167 -0
  6. package/skills/wecom-doc-manager/SKILL.md +106 -0
  7. package/skills/wecom-doc-manager/references/api-create-doc.md +56 -0
  8. package/skills/wecom-doc-manager/references/api-edit-doc-content.md +68 -0
  9. package/skills/wecom-doc-manager/references/api-export-document.md +88 -0
  10. package/skills/wecom-edit-todo/SKILL.md +254 -0
  11. package/skills/wecom-get-todo-detail/SKILL.md +148 -0
  12. package/skills/wecom-get-todo-list/SKILL.md +132 -0
  13. package/skills/wecom-meeting-create/SKILL.md +163 -0
  14. package/skills/wecom-meeting-create/references/example-full.md +30 -0
  15. package/skills/wecom-meeting-create/references/example-reminder.md +46 -0
  16. package/skills/wecom-meeting-create/references/example-security.md +22 -0
  17. package/skills/wecom-meeting-manage/SKILL.md +141 -0
  18. package/skills/wecom-meeting-query/SKILL.md +335 -0
  19. package/skills/wecom-preflight/SKILL.md +103 -0
  20. package/skills/wecom-schedule/SKILL.md +164 -0
  21. package/skills/wecom-schedule/references/api-check-availability.md +56 -0
  22. package/skills/wecom-schedule/references/api-create-schedule.md +38 -0
  23. package/skills/wecom-schedule/references/api-get-schedule-detail.md +81 -0
  24. package/skills/wecom-schedule/references/api-update-schedule.md +30 -0
  25. package/skills/wecom-schedule/references/ref-reminders.md +24 -0
  26. package/skills/wecom-smartsheet-data/SKILL.md +76 -0
  27. package/skills/wecom-smartsheet-data/references/api-get-records.md +61 -0
  28. package/skills/wecom-smartsheet-data/references/cell-value-formats.md +120 -0
  29. package/skills/wecom-smartsheet-schema/SKILL.md +96 -0
  30. package/skills/wecom-smartsheet-schema/references/field-types.md +43 -0
  31. package/wecom/accounts.js +1 -0
  32. package/wecom/callback-inbound.js +133 -33
  33. package/wecom/channel-plugin.js +107 -125
  34. package/wecom/constants.js +83 -3
  35. package/wecom/mcp-config.js +146 -0
  36. package/wecom/mcp-tool.js +660 -0
  37. package/wecom/media-uploader.js +208 -0
  38. package/wecom/openclaw-compat.js +302 -0
  39. package/wecom/reqid-store.js +146 -0
  40. package/wecom/target.js +3 -2
  41. package/wecom/workspace-template.js +107 -21
  42. package/wecom/ws-monitor.js +778 -328
  43. package/image-processor.js +0 -175
@@ -1,4 +1,13 @@
1
- import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "node:fs";
1
+ import {
2
+ copyFileSync,
3
+ existsSync,
4
+ mkdirSync,
5
+ readFileSync,
6
+ readdirSync,
7
+ renameSync,
8
+ statSync,
9
+ writeFileSync,
10
+ } from "node:fs";
2
11
  import { homedir } from "node:os";
3
12
  import { join } from "node:path";
4
13
  import { logger } from "../logger.js";
@@ -19,8 +28,10 @@ function expandTilde(p) {
19
28
 
20
29
  // --- mtime caches for force-reseed ---
21
30
  const _templateMtimeCache = new Map(); // templateDir → { maxMtimeMs, checkedAt }
22
- const _agentSeedMtimeCache = new Map(); // `${templateDir}::${agentId}` → maxMtimeMs
23
31
  const TEMPLATE_MTIME_CACHE_TTL_MS = 60_000;
32
+ const TEMPLATE_STATE_DIRNAME = ".openclaw";
33
+ const TEMPLATE_STATE_FILENAME = "wecom-template-state.json";
34
+ const TEMPLATE_STATE_VERSION = 1;
24
35
 
25
36
  function getTemplateMaxMtimeMs(templateDir) {
26
37
  const now = Date.now();
@@ -43,7 +54,7 @@ function getTemplateMaxMtimeMs(templateDir) {
43
54
 
44
55
  export function clearTemplateMtimeCache({ agentSeedCache = true } = {}) {
45
56
  _templateMtimeCache.clear();
46
- if (agentSeedCache) _agentSeedMtimeCache.clear();
57
+ void agentSeedCache;
47
58
  }
48
59
 
49
60
  /**
@@ -67,9 +78,76 @@ export function getWorkspaceTemplateDir(config) {
67
78
  return config?.channels?.wecom?.workspaceTemplate?.trim() || null;
68
79
  }
69
80
 
81
+ function hasWorkspaceMemoryMarkers(workspaceDir) {
82
+ return existsSync(join(workspaceDir, "memory")) || existsSync(join(workspaceDir, "MEMORY.md"));
83
+ }
84
+
85
+ function resolveTemplateStatePath(workspaceDir) {
86
+ return join(workspaceDir, TEMPLATE_STATE_DIRNAME, TEMPLATE_STATE_FILENAME);
87
+ }
88
+
89
+ function readTemplateState(workspaceDir) {
90
+ const statePath = resolveTemplateStatePath(workspaceDir);
91
+ if (!existsSync(statePath)) {
92
+ return null;
93
+ }
94
+
95
+ try {
96
+ const raw = readFileSync(statePath, "utf8");
97
+ const parsed = JSON.parse(raw);
98
+ if (!parsed || typeof parsed !== "object") {
99
+ return null;
100
+ }
101
+
102
+ return {
103
+ version: parsed.version,
104
+ seededAt: typeof parsed.seededAt === "string" ? parsed.seededAt : null,
105
+ templateDir: typeof parsed.templateDir === "string" ? parsed.templateDir : null,
106
+ seededFiles: Array.isArray(parsed.seededFiles)
107
+ ? parsed.seededFiles.filter((file) => typeof file === "string")
108
+ : [],
109
+ templateMtimeMs:
110
+ typeof parsed.templateMtimeMs === "number" && Number.isFinite(parsed.templateMtimeMs)
111
+ ? parsed.templateMtimeMs
112
+ : null,
113
+ migratedFromLegacy: parsed.migratedFromLegacy === true,
114
+ };
115
+ } catch {
116
+ return null;
117
+ }
118
+ }
119
+
120
+ function writeTemplateState(workspaceDir, state) {
121
+ const statePath = resolveTemplateStatePath(workspaceDir);
122
+ const stateDir = join(workspaceDir, TEMPLATE_STATE_DIRNAME);
123
+ mkdirSync(stateDir, { recursive: true });
124
+
125
+ const payload = `${JSON.stringify(
126
+ {
127
+ version: TEMPLATE_STATE_VERSION,
128
+ seededAt: state.seededAt,
129
+ templateDir: state.templateDir,
130
+ seededFiles: [...new Set(state.seededFiles)].sort(),
131
+ templateMtimeMs: state.templateMtimeMs,
132
+ migratedFromLegacy: state.migratedFromLegacy === true,
133
+ },
134
+ null,
135
+ 2,
136
+ )}\n`;
137
+ const tmpPath = `${statePath}.tmp-${process.pid}-${Date.now().toString(36)}`;
138
+ writeFileSync(tmpPath, payload, "utf8");
139
+ renameSync(tmpPath, statePath);
140
+ }
141
+
142
+ function collectExistingSeededFiles(workspaceDir, templateFiles) {
143
+ return templateFiles.filter((file) => existsSync(join(workspaceDir, file)));
144
+ }
145
+
70
146
  /**
71
- * Copy template files into a newly created agent's workspace directory.
72
- * Only copies files that don't already exist (writeFileIfMissing semantics).
147
+ * Copy selected template files into a dynamic agent workspace.
148
+ * BOOTSTRAP.md may be synced only before user memory markers appear; once the
149
+ * workspace has memory/ or MEMORY.md, bootstrap is treated as completed and is
150
+ * never re-seeded by the plugin.
73
151
  * Silently skips if workspaceTemplate is not configured or directory is missing.
74
152
  *
75
153
  * @param {string} agentId
@@ -89,33 +167,38 @@ export function seedAgentWorkspace(agentId, config, overrideTemplateDir) {
89
167
  }
90
168
 
91
169
  const workspaceDir = resolveAgentWorkspaceDirLocal(agentId);
170
+ const workspaceExistedBefore = existsSync(workspaceDir);
92
171
 
93
172
  try {
94
173
  const templateMaxMtimeMs = getTemplateMaxMtimeMs(templateDir);
95
- const cacheKey = `${templateDir}::${agentId}`;
96
- const lastSyncedMtimeMs = _agentSeedMtimeCache.get(cacheKey) ?? 0;
97
- const isFirstSeed = lastSyncedMtimeMs === 0;
174
+ mkdirSync(workspaceDir, { recursive: true });
98
175
 
99
- if (templateMaxMtimeMs <= lastSyncedMtimeMs && existsSync(workspaceDir)) {
100
- return;
101
- }
176
+ const templateFiles = readdirSync(templateDir).filter((file) => BOOTSTRAP_FILENAMES.has(file));
177
+ let state = readTemplateState(workspaceDir);
178
+ const isLegacyWorkspace = !state && workspaceExistedBefore;
179
+ const isFirstSeed = !state && !workspaceExistedBefore;
102
180
 
103
- mkdirSync(workspaceDir, { recursive: true });
181
+ if (!state) {
182
+ state = {
183
+ version: TEMPLATE_STATE_VERSION,
184
+ seededAt: new Date().toISOString(),
185
+ templateDir,
186
+ seededFiles: isLegacyWorkspace ? collectExistingSeededFiles(workspaceDir, templateFiles) : [],
187
+ templateMtimeMs: templateMaxMtimeMs,
188
+ migratedFromLegacy: isLegacyWorkspace,
189
+ };
190
+ }
104
191
 
105
- const files = readdirSync(templateDir);
106
- for (const file of files) {
107
- if (!BOOTSTRAP_FILENAMES.has(file)) {
192
+ const bootstrapAllowed = !hasWorkspaceMemoryMarkers(workspaceDir);
193
+ for (const file of templateFiles) {
194
+ if (file === "BOOTSTRAP.md" && !bootstrapAllowed) {
108
195
  continue;
109
196
  }
110
197
  const src = join(templateDir, file);
111
198
  const dest = join(workspaceDir, file);
112
199
  if (existsSync(dest)) {
113
200
  if (!isFirstSeed) {
114
- const srcMtimeMs = statSync(src).mtimeMs;
115
- const destMtimeMs = statSync(dest).mtimeMs;
116
- if (srcMtimeMs <= destMtimeMs) {
117
- continue;
118
- }
201
+ continue;
119
202
  }
120
203
  copyFileSync(src, dest);
121
204
  logger.info("WeCom: re-seeded workspace file", { agentId, file, isFirstSeed });
@@ -123,9 +206,12 @@ export function seedAgentWorkspace(agentId, config, overrideTemplateDir) {
123
206
  copyFileSync(src, dest);
124
207
  logger.info("WeCom: seeded workspace file", { agentId, file });
125
208
  }
209
+ state.seededFiles.push(file);
126
210
  }
127
211
 
128
- _agentSeedMtimeCache.set(cacheKey, templateMaxMtimeMs);
212
+ state.templateDir = templateDir;
213
+ state.templateMtimeMs = templateMaxMtimeMs;
214
+ writeTemplateState(workspaceDir, state);
129
215
  } catch (err) {
130
216
  logger.warn("WeCom: failed to seed agent workspace", {
131
217
  agentId,