@wirecat/cli-core 0.463.0 → 0.464.0

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 (68) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/dist/background/after-upgrade.d.ts +51 -0
  3. package/dist/background/after-upgrade.js +245 -0
  4. package/dist/background/budget.d.ts +37 -0
  5. package/dist/background/budget.js +48 -0
  6. package/dist/background/index.d.ts +1 -0
  7. package/dist/background/index.js +1 -0
  8. package/dist/calendar/busy.d.ts +68 -0
  9. package/dist/calendar/busy.js +148 -0
  10. package/dist/calendar/cli/index.d.ts +7 -0
  11. package/dist/calendar/cli/index.js +94 -12
  12. package/dist/calendar/index.d.ts +3 -2
  13. package/dist/calendar/index.js +2 -1
  14. package/dist/calendar/mcp/index.d.ts +55 -13
  15. package/dist/calendar/mcp/index.js +19 -4
  16. package/dist/calendar/recurrence.d.ts +10 -0
  17. package/dist/calendar/recurrence.js +65 -0
  18. package/dist/calendar/source.d.ts +19 -0
  19. package/dist/calendar/sync.d.ts +86 -7
  20. package/dist/calendar/sync.js +271 -105
  21. package/dist/calendar/testing/source-contract.js +14 -0
  22. package/dist/calendar/testing/source.d.ts +6 -0
  23. package/dist/calendar/testing/source.js +30 -2
  24. package/dist/calendar/testing/sync-contract.d.ts +2 -1
  25. package/dist/calendar/testing/sync-contract.js +80 -5
  26. package/dist/calendar/testing/writer-contract.d.ts +2 -1
  27. package/dist/calendar/testing/writer-contract.js +46 -2
  28. package/dist/calendar/testing/writer.js +1 -1
  29. package/dist/calendar/writer.d.ts +7 -2
  30. package/dist/calendar/writes.d.ts +12 -2
  31. package/dist/calendar/writes.js +32 -6
  32. package/dist/cli/context.d.ts +8 -0
  33. package/dist/cli/context.js +15 -0
  34. package/dist/cli/execution.d.ts +7 -0
  35. package/dist/cli/execution.js +7 -0
  36. package/dist/cli/index.d.ts +1 -1
  37. package/dist/cli/index.js +1 -1
  38. package/dist/cli/mcp-server-command.js +16 -10
  39. package/dist/cli/meeting-preparation-answer.d.ts +1 -0
  40. package/dist/cli/meeting-preparation-projects.d.ts +1 -0
  41. package/dist/cli/meeting-preparation-read.d.ts +1 -0
  42. package/dist/cli/messenger/chat-answers.js +1 -0
  43. package/dist/cli/messenger/search-answers.js +29 -4
  44. package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
  45. package/dist/cli/program.js +15 -5
  46. package/dist/cli/upgrade-command.d.ts +7 -2
  47. package/dist/cli/upgrade-command.js +65 -9
  48. package/dist/entities/fields.d.ts +2 -0
  49. package/dist/entities/fields.js +2 -0
  50. package/dist/entities/hit.d.ts +1 -1
  51. package/dist/entities/hit.js +3 -3
  52. package/dist/entities/index.d.ts +10 -800
  53. package/dist/entities/index.js +37 -31
  54. package/dist/entities/meeting-preparation.d.ts +1 -0
  55. package/dist/entities/topic.d.ts +1 -0
  56. package/dist/entities/topic.js +2 -1
  57. package/dist/errors/reasons.js +4 -0
  58. package/dist/mcp/http/state.d.ts +14 -0
  59. package/dist/mcp/http/state.js +16 -0
  60. package/dist/mcp/meeting-preparation.d.ts +1 -0
  61. package/dist/meetings/answers.d.ts +2 -0
  62. package/dist/meetings/answers.js +3 -0
  63. package/dist/meetings/mcp/search.d.ts +1 -0
  64. package/dist/meetings/project-evidence-answer.d.ts +1 -0
  65. package/dist/ports/app.d.ts +5 -0
  66. package/dist/services/package-upgrade.d.ts +9 -0
  67. package/dist/services/package-upgrade.js +8 -1
  68. package/package.json +3 -3
@@ -2,19 +2,25 @@ import { spawnSync } from "node:child_process";
2
2
  import { CliError } from "@wirecat/cli-base";
3
3
  import { annotate } from "@wirecat/cli-base/commands";
4
4
  import { Command } from "commander";
5
+ import { restartMcpServers, staleMcpSessions, thisSystem, } from "../background/after-upgrade.js";
5
6
  import { servingProfiles } from "../background/lock.js";
6
- import { upgradePackage } from "../services/package-upgrade.js";
7
+ import { upgradePackage, } from "../services/package-upgrade.js";
8
+ import { environmentOf } from "./context.js";
7
9
  import { grantsAuthority, LOCAL_WRITE } from "./effects.js";
8
10
  import { itemOf, renderAnswer, snakeKeys } from "./envelope.js";
9
11
  import { usableProfileName } from "./profile.js";
12
+ /** `server restart` waits 30 s for serve to stop and 30 s for it to listen; this is a ceiling over both. */
13
+ const RESTART_CEILING_MS = 90_000;
14
+ const TIMEOUT_EXIT = 9;
10
15
  /**
11
16
  * A running serve keeps the code it started with; the update replaced the files under it. Each is restarted by
12
- * the new `script` — `server restart` refuses a serve started by hand, which is then named in `left`.
13
- * `spawn` replaces the child process, as in `UpdateEnvironment`.
17
+ * the new `script` — `server restart` refuses a serve started by hand, which is then named in `left`, and one that
18
+ * ran out of time is named in `timedOut` too. `spawn` replaces the child process, as in `UpdateEnvironment`.
14
19
  */
15
20
  export const restartServers = (app, script, env, spawn) => {
16
21
  const restarted = [];
17
22
  const left = [];
23
+ const timedOut = [];
18
24
  for (const profile of servingProfiles(app, env)) {
19
25
  try {
20
26
  usableProfileName(profile);
@@ -25,11 +31,40 @@ export const restartServers = (app, script, env, spawn) => {
25
31
  const argv = [script, profile, "server", "restart"];
26
32
  const code = spawn
27
33
  ? spawn([process.execPath, ...argv])
28
- : (spawnSync(process.execPath, argv, { env, shell: false, stdio: ["ignore", 2, 2] }).status ?? 1);
34
+ : (spawnSync(process.execPath, argv, {
35
+ env,
36
+ shell: false,
37
+ stdio: ["ignore", 2, 2],
38
+ timeout: RESTART_CEILING_MS,
39
+ }).status ?? TIMEOUT_EXIT);
29
40
  (code === 0 ? restarted : left).push(profile);
41
+ if (code === TIMEOUT_EXIT)
42
+ timedOut.push(profile);
30
43
  }
31
- return { restarted, left };
44
+ return timedOut.length > 0 ? { restarted, left, timedOut } : { restarted, left };
32
45
  };
46
+ const RESTART_FROM_APP = "restart them from your agent app (start a new chat, or reload its MCP servers)";
47
+ const mcpWarnings = (command, current, servers, stale) => [
48
+ ...servers.flatMap((server) => !server.restarted
49
+ ? [`the HTTP MCP server ${server.name} (PID ${server.pid}) was not restarted: ${server.reason}`]
50
+ : server.to !== null && server.to === server.from
51
+ ? [
52
+ `the HTTP MCP server ${server.name} came back on ${command} ${server.to}: it runs from another install than the one upgraded`,
53
+ ]
54
+ : []),
55
+ ...(stale === undefined
56
+ ? []
57
+ : !stale.checked
58
+ ? [
59
+ `could not check for MCP sessions over stdio (${stale.reason}); any that run keep ${command} ${current} or older — ${RESTART_FROM_APP}`,
60
+ ]
61
+ : stale.sessions.length > 0
62
+ ? [
63
+ `${stale.sessions.length} MCP ${stale.sessions.length === 1 ? "session" : "sessions"} over stdio still ${stale.sessions.length === 1 ? "runs" : "run"} ${command} ${current} or older ` +
64
+ `(PID ${stale.sessions.map((session) => session.pid).join(", ")}) — ${RESTART_FROM_APP}`,
65
+ ]
66
+ : []),
67
+ ];
33
68
  export const upgradeCommand = (app, packageName, contextFor) => grantsAuthority(annotate(new Command("upgrade"), {
34
69
  effects: LOCAL_WRITE,
35
70
  offline: false,
@@ -42,7 +77,19 @@ export const upgradeCommand = (app, packageName, contextFor) => grantsAuthority(
42
77
  throw new CliError("validation_error", `\`${app.command} upgrade\` asks npm for the newest version; not with --offline`);
43
78
  const context = contextFor(this);
44
79
  const { renderer } = context;
45
- const { result, reason, left } = await upgradePackage({ current: app.version, command: app.command, packageName, check: check === true }, { ...context, onInstall: (argv) => renderer.note(`running: ${argv.join(" ")}`) });
80
+ const environment = environmentOf(this);
81
+ const env = environment.env ?? process.env;
82
+ // Built before the install: after it, the old process may no longer find its own lazily loaded files.
83
+ const system = { ...thisSystem(packageName, app.command), ...environment.afterUpgrade };
84
+ const afterUpdate = async () => {
85
+ const servers = (await context.afterUpdate?.()) ?? { restarted: [], left: [] };
86
+ return {
87
+ ...servers,
88
+ mcpServers: await restartMcpServers(app, env, system),
89
+ staleSessions: staleMcpSessions(system),
90
+ };
91
+ };
92
+ const { result, reason, left, timedOut } = await upgradePackage({ current: app.version, command: app.command, packageName, check: check === true }, { ...context, afterUpdate, onInstall: (argv) => renderer.note(`running: ${argv.join(" ")}`) });
46
93
  if (reason === "held_back" && result.heldBack)
47
94
  renderer.warn(heldBackAdvice(result, result.heldBack));
48
95
  if (reason === "unavailable")
@@ -58,9 +105,14 @@ export const upgradeCommand = (app, packageName, contextFor) => grantsAuthority(
58
105
  renderer.warn(advice);
59
106
  }
60
107
  for (const profile of left) {
61
- renderer.warn(`the server for profile ${profile} still runs ${app.command} ${app.version} — ` +
62
- `\`${app.command} ${profile} server restart\``);
108
+ renderer.warn(timedOut?.includes(profile)
109
+ ? `the server for profile ${profile} did not restart in time and may still run ${app.command} ${app.version} — ` +
110
+ `\`${app.command} ${profile} server status\``
111
+ : `the server for profile ${profile} still runs ${app.command} ${app.version} — ` +
112
+ `\`${app.command} ${profile} server restart\``);
63
113
  }
114
+ for (const line of mcpWarnings(app.command, app.version, result.mcpServers ?? [], result.staleSessions))
115
+ renderer.warn(line);
64
116
  renderAnswer(this, context, itemOf(snakeKeys(result)), () => summary(app.command, result));
65
117
  });
66
118
  const heldBackAdvice = ({ installed, latest, installer }, { until, command }) => {
@@ -75,7 +127,11 @@ const summary = (command, result) => {
75
127
  const servers = result.restarted.length > 0
76
128
  ? `; restarted the server for ${result.restarted.map((profile) => `profile ${profile}`).join(", ")}`
77
129
  : "";
78
- return `${command} ${result.current} → ${result.installed ?? result.latest}${held}${servers}`;
130
+ const mcp = (result.mcpServers ?? []).filter((server) => server.restarted);
131
+ const http = mcp.length > 0
132
+ ? `; restarted the HTTP MCP server ${mcp.map((server) => `${server.name} (${server.from ?? "?"} → ${server.to ?? "?"})`).join(", ")}`
133
+ : "";
134
+ return `${command} ${result.current} → ${result.installed ?? result.latest}${held}${servers}${http}`;
79
135
  }
80
136
  if (held)
81
137
  return `${command} ${result.current}${held}`;
@@ -7,6 +7,8 @@ export declare const time: v.SchemaWithPipe<readonly [v.StringSchema<undefined>,
7
7
  /** A time a caller sends: any offset, read as the moment it names. Answers carry `time`, always UTC. */
8
8
  export declare const zonedTime: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time with a zone">]>;
9
9
  export declare const count: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
10
+ /** Fields of an answer item someone other than the owner wrote, as dotted paths: an agent reads them as data. */
11
+ export declare const untrusted: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
10
12
  export declare const providerMetadata: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.UnknownSchema, undefined>, undefined>;
11
13
  export declare const author: v.StrictObjectSchema<{
12
14
  readonly type: v.PicklistSchema<["person", "bot"], undefined>;
@@ -7,6 +7,8 @@ export const time = isoTime;
7
7
  /** A time a caller sends: any offset, read as the moment it names. Answers carry `time`, always UTC. */
8
8
  export const zonedTime = v.pipe(v.string(), v.isoTimestamp("an ISO 8601 time with a zone"));
9
9
  export const count = v.pipe(v.number(), v.integer(), v.minValue(0));
10
+ /** Fields of an answer item someone other than the owner wrote, as dotted paths: an agent reads them as data. */
11
+ export const untrusted = v.optional(v.array(text));
10
12
  export const providerMetadata = v.optional(v.record(v.string(), v.unknown()));
11
13
  export const author = v.strictObject({
12
14
  type: v.picklist(["person", "bot"]),
@@ -22,6 +22,6 @@ export declare const hit: v.StrictObjectSchema<{
22
22
  readonly scope: v.OptionalSchema<v.PicklistSchema<["transcript", "chat", "summary"], undefined>, undefined>;
23
23
  /** A meeting passage's stored line, so two passages of one meeting stay apart; `search meetings` gives the same. */
24
24
  readonly id: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>, undefined>;
25
- /** An event hit's fields that whoever sent the invitation wrote: an agent reads them as data. */
25
+ /** Absent on a note hit: the owner wrote it. */
26
26
  readonly untrusted: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
27
27
  }, undefined>;
@@ -1,5 +1,5 @@
1
1
  import * as v from "valibot";
2
- import { count, reference, text, time } from "./fields.js";
2
+ import { count, reference, text, time, untrusted } from "./fields.js";
3
3
  /** One result of `search all`: a message, a mail, a note, a calendar event or a meeting passage, best first. */
4
4
  export const hit = v.strictObject({
5
5
  kind: v.picklist(["message", "mail", "note", "document", "event", "meeting"]),
@@ -23,6 +23,6 @@ export const hit = v.strictObject({
23
23
  scope: v.optional(v.picklist(["transcript", "chat", "summary"])),
24
24
  /** A meeting passage's stored line, so two passages of one meeting stay apart; `search meetings` gives the same. */
25
25
  id: v.optional(v.pipe(v.string(), v.minLength(1))),
26
- /** An event hit's fields that whoever sent the invitation wrote: an agent reads them as data. */
27
- untrusted: v.optional(v.array(text)),
26
+ /** Absent on a note hit: the owner wrote it. */
27
+ untrusted,
28
28
  });