jowork 0.2.3 → 0.2.4

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.
@@ -83,7 +83,10 @@ function createJoWorkMcpServer(opts) {
83
83
  return { isStale: true, lastSync: null, agoMinutes: null, hint: "cannot check freshness" };
84
84
  }
85
85
  }
86
+ let lastAutoSyncAt = 0;
87
+ const AUTO_SYNC_COOLDOWN_MS = 5 * 60 * 1e3;
86
88
  async function autoSyncIfStale(source) {
89
+ if (Date.now() - lastAutoSyncAt < AUTO_SYNC_COOLDOWN_MS) return false;
87
90
  const freshness = getDataFreshness(source);
88
91
  if (!freshness.isStale) return false;
89
92
  try {
@@ -110,6 +113,7 @@ function createJoWorkMcpServer(opts) {
110
113
  } catch {
111
114
  }
112
115
  }
116
+ lastAutoSyncAt = Date.now();
113
117
  return true;
114
118
  } catch {
115
119
  return false;
@@ -132,7 +136,8 @@ function createJoWorkMcpServer(opts) {
132
136
  if (path) {
133
137
  source = "local";
134
138
  }
135
- const pathFilter = path ? ` AND o.uri LIKE 'local://${escapeLike(path)}%'` : "";
139
+ const pathFilter = path ? ` AND o.uri LIKE ?` : "";
140
+ const pathParam = path ? `local://${escapeLike(path)}%` : null;
136
141
  const ftsMatchQuery = buildFtsQuery(query);
137
142
  if (ftsMatchQuery) {
138
143
  try {
@@ -141,7 +146,9 @@ function createJoWorkMcpServer(opts) {
141
146
  WHERE objects_fts MATCH ? AND o.source = ?${pathFilter} LIMIT ?` : `SELECT o.id, o.title, o.summary, o.source, o.source_type, o.uri, o.tags, o.file_path
142
147
  FROM objects_fts JOIN objects o ON o.rowid = objects_fts.rowid
143
148
  WHERE objects_fts MATCH ?${pathFilter} LIMIT ?`;
144
- const ftsArgs = source ? [ftsMatchQuery, source, limit] : [ftsMatchQuery, limit];
149
+ const ftsArgs = source ? [ftsMatchQuery, source] : [ftsMatchQuery];
150
+ if (pathParam) ftsArgs.push(pathParam);
151
+ ftsArgs.push(limit);
145
152
  const ftsResults = sqlite.prepare(ftsQuery).all(...ftsArgs);
146
153
  if (ftsResults.length > 0) {
147
154
  logInfo("mcp", `search_data: "${query}" (FTS)`, { source, path, resultCount: ftsResults.length, ms: Date.now() - t0 });
@@ -155,7 +162,7 @@ Showing ${ftsResults.length} results (summaries only). Use fetch_content with a
155
162
  }
156
163
  }
157
164
  const cleanedQuery = query.replace(/飞书|feishu|lark|github|gitlab|notion|slack/gi, "").replace(/群里|最近|在|讨论|什么|话题|有哪些|是什么|怎么样|帮我|告诉我|查一下/g, "").trim();
158
- const likePathFilter = path ? ` AND uri LIKE 'local://${escapeLike(path)}%'` : "";
165
+ const likePathFilter = path ? ` AND uri LIKE ?` : "";
159
166
  let rows = [];
160
167
  if (source && cleanedQuery.length >= 2) {
161
168
  const segments = cleanedQuery.split(/\s+/).filter((s) => s.length >= 2);
@@ -166,7 +173,9 @@ Showing ${ftsResults.length} results (summaries only). Use fetch_content with a
166
173
  const p = `%${escapeLike(seg)}%`;
167
174
  params.push(p, p, p);
168
175
  }
169
- params.push(source, limit);
176
+ params.push(source);
177
+ if (pathParam) params.push(pathParam);
178
+ params.push(limit);
170
179
  rows = sqlite.prepare(`
171
180
  SELECT id, title, summary, source, source_type, uri, tags, file_path FROM objects
172
181
  WHERE (${conditions}) AND source = ?${likePathFilter} ORDER BY last_synced_at DESC LIMIT ?
@@ -175,10 +184,13 @@ Showing ${ftsResults.length} results (summaries only). Use fetch_content with a
175
184
  rows = [];
176
185
  }
177
186
  } else if (source) {
187
+ const srcParams = [source];
188
+ if (pathParam) srcParams.push(pathParam);
189
+ srcParams.push(limit);
178
190
  rows = sqlite.prepare(`
179
191
  SELECT id, title, summary, source, source_type, uri, tags, file_path FROM objects
180
192
  WHERE source = ?${likePathFilter} ORDER BY last_synced_at DESC LIMIT ?
181
- `).all(source, limit);
193
+ `).all(...srcParams);
182
194
  } else if (cleanedQuery.length >= 2) {
183
195
  const pattern = `%${escapeLike(cleanedQuery)}%`;
184
196
  rows = sqlite.prepare(`
@@ -763,9 +775,37 @@ ${hot.summary}
763
775
  `SELECT source, COUNT(*) as count, MAX(last_synced_at) as last_sync FROM objects GROUP BY source`
764
776
  ).all();
765
777
  parts.push("## Data Sources\n");
778
+ const staleSources = [];
766
779
  for (const s of sources) {
767
780
  const ago = Math.round((now - s.last_sync) / 6e4);
768
- parts.push(`- ${s.source}: ${s.count} objects (last sync: ${ago} min ago)`);
781
+ const staleTag = ago > 60 ? " \u26A0 STALE" : "";
782
+ if (ago > 60) staleSources.push(s.source);
783
+ parts.push(`- ${s.source}: ${s.count} objects (last sync: ${ago} min ago${staleTag})`);
784
+ }
785
+ try {
786
+ const { listCredentials } = await import("./credential-store-ZRZCSRPC.js");
787
+ const connected = listCredentials();
788
+ const synced = new Set(sources.map((s) => s.source));
789
+ const neverSynced = connected.filter((c) => !synced.has(c));
790
+ for (const ns of neverSynced) {
791
+ parts.push(`- ${ns}: \u26A0 NEVER SYNCED`);
792
+ staleSources.push(ns);
793
+ }
794
+ } catch {
795
+ }
796
+ parts.push("\n## Recommended Actions\n");
797
+ if (staleSources.length > 0) {
798
+ parts.push(`- **SYNC NOW**: ${staleSources.join(", ")} data is stale. Call sync_now tool immediately.`);
799
+ }
800
+ if (goals.length === 0) {
801
+ parts.push("- **SET GOALS**: No goals configured. Ask user about their key objectives and create goals with update_goal.");
802
+ }
803
+ const memCount = sqlite.prepare("SELECT COUNT(*) as c FROM memories").get().c;
804
+ if (memCount === 0) {
805
+ parts.push("- **START MEMORY**: No memories saved yet. Save user preferences and decisions as you learn them via write_memory.");
806
+ }
807
+ if (staleSources.length === 0 && goals.length > 0 && memCount > 0) {
808
+ parts.push("- All systems healthy. No action needed.");
769
809
  }
770
810
  return { content: [{ type: "text", text: parts.join("\n") }] };
771
811
  }
@@ -773,7 +813,7 @@ ${hot.summary}
773
813
  server.tool(
774
814
  "sync_now",
775
815
  {
776
- source: z.string().optional().describe("Sync a specific source (feishu, github, etc.) or omit for all")
816
+ source: z.string().optional().describe("Sync a specific source (feishu, github, etc.) or omit for all. Call this directly when data is stale \u2014 do NOT ask the user for permission to sync.")
777
817
  },
778
818
  async ({ source }) => {
779
819
  const freshness = getDataFreshness(source);
package/dist/cli.js CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  } from "./chunk-XAEGXSEO.js";
16
16
  import {
17
17
  createJoWorkMcpServer
18
- } from "./chunk-YVPWTH6F.js";
18
+ } from "./chunk-7U3SXINY.js";
19
19
  import {
20
20
  GoalManager
21
21
  } from "./chunk-TN327MDF.js";
@@ -1950,7 +1950,7 @@ function dashboardCommand(program2) {
1950
1950
  console.error("Error: JoWork not initialized. Run `jowork init` first.");
1951
1951
  process.exit(1);
1952
1952
  }
1953
- const { startDashboard } = await import("./server-6WYDERK6.js");
1953
+ const { startDashboard } = await import("./server-5GVWN2NB.js");
1954
1954
  const port = opts.port ? parseInt(opts.port, 10) : void 0;
1955
1955
  const dashboard = await startDashboard({ port });
1956
1956
  const url = `http://127.0.0.1:${dashboard.port}`;
@@ -793,7 +793,7 @@ function focusMacOS(pid) {
793
793
  } catch {
794
794
  return { focused: false, method: "no-process" };
795
795
  }
796
- if (!tty || tty === "??") {
796
+ if (!tty || tty === "??" || !/^[a-zA-Z0-9/]+$/.test(tty)) {
797
797
  return { focused: false, method: "no-tty" };
798
798
  }
799
799
  let parentComm = "";
package/dist/transport.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createJoWorkMcpServer
3
- } from "./chunk-YVPWTH6F.js";
3
+ } from "./chunk-7U3SXINY.js";
4
4
  import "./chunk-TN327MDF.js";
5
5
  import {
6
6
  dbPath
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jowork",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "AI Agent Infrastructure — let AI agents truly understand your work. Connect data sources, give agents awareness and goals. Local-first, one command.",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0",