@symerian/symi 2.8.10 → 2.8.11

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 (79) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  3. package/dist/{command-registry-B6spVZMd.js → command-registry-BZfKQQev.js} +4 -4
  4. package/dist/{completion-cli-VQrV_JN9.js → completion-cli-B1JkGibL.js} +2 -2
  5. package/dist/{completion-cli-DKoZNVbW.js → completion-cli-B4YP0Otu.js} +1 -1
  6. package/dist/{doctor-completion-BR5k35Qj.js → doctor-completion-CjlRDZow.js} +1 -1
  7. package/dist/{doctor-completion-CPFKFTc-.js → doctor-completion-CophMO9P.js} +1 -1
  8. package/dist/entry.js +1 -1
  9. package/dist/{gateway-cli-BF1-DFEf.js → gateway-cli-CAPtJ_VJ.js} +3 -3
  10. package/dist/{gateway-cli-BJH2K8fn.js → gateway-cli-Dlcx0rsS.js} +3 -3
  11. package/dist/{glass-ui-ws-B7j7iTDg.js → glass-ui-ws-GEiZsdru.js} +1 -1
  12. package/dist/{glass-ui-ws-DyoHQW3O.js → glass-ui-ws-hIgOh0Ub.js} +1 -1
  13. package/dist/index.js +1 -1
  14. package/dist/{onboard-D2NC88Mp.js → onboard-Bub0X2iR.js} +1 -1
  15. package/dist/{onboard-D_2-lyyt.js → onboard-CNiwcGJT.js} +1 -1
  16. package/dist/{onboarding-D7N2AfyJ.js → onboarding-CvK25SU5.js} +1 -1
  17. package/dist/{onboarding-uVLsv2Sd.js → onboarding-DFNiU6QZ.js} +1 -1
  18. package/dist/{onboarding.finalize-CTDv0xwp.js → onboarding.finalize-CvXSAjuU.js} +4 -4
  19. package/dist/{onboarding.finalize-DEjTjgRt.js → onboarding.finalize-_r8l4FJ1.js} +3 -3
  20. package/dist/{program-viRCc6Je.js → program-DtyqJT55.js} +2 -2
  21. package/dist/{program-context-Dun-c3nf.js → program-context-D0rHgaWi.js} +6 -6
  22. package/dist/{prompt-select-styled-DVLibGeR.js → prompt-select-styled-DqjIswKp.js} +1 -1
  23. package/dist/{prompt-select-styled-W2G26g34.js → prompt-select-styled-Dw0GkMPo.js} +1 -1
  24. package/dist/{register.maintenance-DqJL_QWT.js → register.maintenance-D67PVhC5.js} +4 -4
  25. package/dist/{register.maintenance-BGiYxRYm.js → register.maintenance-TB5RRHQv.js} +5 -5
  26. package/dist/{register.onboard-CfySx27T.js → register.onboard-BPcP1dJD.js} +2 -2
  27. package/dist/{register.onboard-848EXgTB.js → register.onboard-jbp1Eeea.js} +2 -2
  28. package/dist/{register.setup-C-NYSAGY.js → register.setup-D4QeCLuQ.js} +2 -2
  29. package/dist/{register.setup-B40A19lI.js → register.setup-Le3LEqeh.js} +2 -2
  30. package/dist/{register.subclis-kF8KnNuq.js → register.subclis-CTnvwrsP.js} +3 -3
  31. package/dist/{run-main-BqpvNq8c.js → run-main-DmgESA-W.js} +3 -3
  32. package/dist/{server-methods-DjB0hxeW.js → server-methods-EJZ_r3g7.js} +31 -7
  33. package/dist/{server-methods-CkLzZq0Y.js → server-methods-kM6zF8qf.js} +31 -7
  34. package/dist/{update-cli-BCFHfhUW.js → update-cli-NRTVUdGw.js} +5 -5
  35. package/dist/{update-cli-B_Mxicbw.js → update-cli-sTB4AuZ6.js} +4 -4
  36. package/docs/reference/templates/AGENTS.md +14 -0
  37. package/extensions/bluebubbles/package.json +1 -1
  38. package/extensions/copilot-proxy/package.json +1 -1
  39. package/extensions/diagnostics-otel/package.json +1 -1
  40. package/extensions/discord/package.json +1 -1
  41. package/extensions/feishu/package.json +1 -1
  42. package/extensions/google-antigravity-auth/package.json +1 -1
  43. package/extensions/google-gemini-cli-auth/package.json +1 -1
  44. package/extensions/googlechat/package.json +1 -1
  45. package/extensions/imessage/package.json +1 -1
  46. package/extensions/irc/package.json +1 -1
  47. package/extensions/learning-loop/package.json +1 -1
  48. package/extensions/line/package.json +1 -1
  49. package/extensions/llm-task/package.json +1 -1
  50. package/extensions/matrix/CHANGELOG.md +6 -0
  51. package/extensions/matrix/package.json +1 -1
  52. package/extensions/mattermost/package.json +1 -1
  53. package/extensions/memory-core/package.json +1 -1
  54. package/extensions/memory-lancedb/package.json +1 -1
  55. package/extensions/minimax-portal-auth/package.json +1 -1
  56. package/extensions/msteams/CHANGELOG.md +6 -0
  57. package/extensions/msteams/package.json +1 -1
  58. package/extensions/nextcloud-talk/package.json +1 -1
  59. package/extensions/nostr/CHANGELOG.md +6 -0
  60. package/extensions/nostr/package.json +1 -1
  61. package/extensions/open-prose/package.json +1 -1
  62. package/extensions/outlook/index.ts +69 -5
  63. package/extensions/outlook/package.json +1 -1
  64. package/extensions/outlook/src/store.ts +118 -11
  65. package/extensions/pipeline/package.json +1 -1
  66. package/extensions/signal/package.json +1 -1
  67. package/extensions/slack/package.json +1 -1
  68. package/extensions/telegram/package.json +1 -1
  69. package/extensions/tlon/package.json +1 -1
  70. package/extensions/twitch/CHANGELOG.md +6 -0
  71. package/extensions/twitch/package.json +1 -1
  72. package/extensions/voice-call/CHANGELOG.md +6 -0
  73. package/extensions/voice-call/package.json +1 -1
  74. package/extensions/whatsapp/package.json +1 -1
  75. package/extensions/zalo/CHANGELOG.md +6 -0
  76. package/extensions/zalo/package.json +1 -1
  77. package/extensions/zalouser/CHANGELOG.md +6 -0
  78. package/extensions/zalouser/package.json +1 -1
  79. package/package.json +1 -1
@@ -3367,11 +3367,24 @@ const browserHandlers = { "browser.request": async ({ params, respond, context }
3367
3367
 
3368
3368
  //#endregion
3369
3369
  //#region src/gateway/server-methods/channels.ts
3370
+ /**
3371
+ * Same 24h window as extensions/outlook/src/store.ts:REFRESH_TRUST_WINDOW_MS.
3372
+ * Kept in sync by convention rather than shared import — importing from an
3373
+ * extension into gateway code would invert the dependency direction. If
3374
+ * either value changes, update the other.
3375
+ */
3376
+ const OUTLOOK_REFRESH_TRUST_WINDOW_MS = 1440 * 60 * 1e3;
3377
+ function resolveOutlookHome() {
3378
+ const home = os.homedir();
3379
+ if (home && home.length > 0) return home;
3380
+ return process.env.HOME || process.env.USERPROFILE || null;
3381
+ }
3370
3382
  function readOutlookConnectionState() {
3371
- const home = process.env.HOME || process.env.USERPROFILE;
3383
+ const home = resolveOutlookHome();
3372
3384
  if (!home) return {
3373
3385
  configured: false,
3374
- connected: false
3386
+ connected: false,
3387
+ connectionState: "not-connected"
3375
3388
  };
3376
3389
  const credsPath = path.join(home, ".symi", "credentials", "outlook.json");
3377
3390
  let raw;
@@ -3380,7 +3393,9 @@ function readOutlookConnectionState() {
3380
3393
  } catch {
3381
3394
  return {
3382
3395
  configured: false,
3383
- connected: false
3396
+ connected: false,
3397
+ connectionState: "not-connected",
3398
+ credentialsPath: credsPath
3384
3399
  };
3385
3400
  }
3386
3401
  let creds;
@@ -3389,21 +3404,30 @@ function readOutlookConnectionState() {
3389
3404
  } catch {
3390
3405
  return {
3391
3406
  configured: true,
3392
- connected: false
3407
+ connected: false,
3408
+ connectionState: "not-connected",
3409
+ credentialsPath: credsPath
3393
3410
  };
3394
3411
  }
3395
3412
  const expires = typeof creds.expires === "number" ? creds.expires : 0;
3396
3413
  const hasRefresh = typeof creds.refresh === "string" && creds.refresh.length > 0;
3397
- const notExpired = Date.now() < expires;
3398
- const connected = notExpired || hasRefresh;
3414
+ const lastVerifiedAtMs = typeof creds.lastVerifiedAt === "number" ? creds.lastVerifiedAt : void 0;
3415
+ const now = Date.now();
3416
+ const notExpired = now < expires;
3417
+ const recentlyVerified = typeof lastVerifiedAtMs === "number" && now - lastVerifiedAtMs < OUTLOOK_REFRESH_TRUST_WINDOW_MS;
3418
+ const connectionState = notExpired ? "valid" : recentlyVerified ? "trusted" : hasRefresh ? "stale" : "not-connected";
3419
+ const connected = connectionState !== "not-connected";
3399
3420
  const tokenState = notExpired ? "valid" : hasRefresh ? "expired-will-refresh" : "expired-no-refresh";
3400
3421
  return {
3401
3422
  configured: true,
3402
3423
  connected,
3424
+ connectionState,
3403
3425
  email: typeof creds.email === "string" ? creds.email : void 0,
3404
3426
  displayName: typeof creds.displayName === "string" ? creds.displayName : void 0,
3405
3427
  tokenState,
3406
- updatedAt: typeof creds.updatedAt === "string" ? creds.updatedAt : void 0
3428
+ updatedAt: typeof creds.updatedAt === "string" ? creds.updatedAt : void 0,
3429
+ lastVerifiedAt: typeof lastVerifiedAtMs === "number" ? new Date(lastVerifiedAtMs).toISOString() : void 0,
3430
+ credentialsPath: credsPath
3407
3431
  };
3408
3432
  }
3409
3433
  async function logoutChannelAccount(params) {
@@ -112,10 +112,10 @@ import "./npm-registry-spec-PuS2I1Em.js";
112
112
  import "./skill-scanner-BV3QHmsf.js";
113
113
  import "./installs-B4sNNRaW.js";
114
114
  import "./channels-status-issues-B-ssmZ8E.js";
115
- import "./register.subclis-kF8KnNuq.js";
116
- import "./command-registry-B6spVZMd.js";
115
+ import "./register.subclis-CTnvwrsP.js";
116
+ import "./command-registry-BZfKQQev.js";
117
117
  import "./program-context-DeZ44oQ9.js";
118
- import { r as installCompletion } from "./completion-cli-VQrV_JN9.js";
118
+ import { r as installCompletion } from "./completion-cli-B1JkGibL.js";
119
119
  import "./daemon-runtime-D_elkkFW.js";
120
120
  import { r as parseSemver } from "./runtime-guard-BofkqBu7.js";
121
121
  import "./systemd-CJ5L2ee4.js";
@@ -136,8 +136,8 @@ import { n as updateNpmInstalledPlugins, t as syncPluginsForUpdateChannel } from
136
136
  import "./doctor-config-flow-B2dVxbL7.js";
137
137
  import "./systemd-linger-ipoPa7o0.js";
138
138
  import "./health-format-DkjSgkDx.js";
139
- import { n as doctorCommand, t as selectStyled } from "./prompt-select-styled-DVLibGeR.js";
140
- import { r as ensureCompletionCacheExists, t as checkShellCompletionStatus } from "./doctor-completion-BR5k35Qj.js";
139
+ import { n as doctorCommand, t as selectStyled } from "./prompt-select-styled-DqjIswKp.js";
140
+ import { r as ensureCompletionCacheExists, t as checkShellCompletionStatus } from "./doctor-completion-CjlRDZow.js";
141
141
  import { spawn, spawnSync } from "node:child_process";
142
142
  import os from "node:os";
143
143
  import path from "node:path";
@@ -107,7 +107,7 @@ import "./stagger-CZ1Rrj7O.js";
107
107
  import { c as resolveGatewayLaunchAgentLabel, d as resolveGatewaySystemdServiceName, f as resolveGatewayWindowsTaskName } from "./constants-DF8wPn-_.js";
108
108
  import "./channel-selection-9fIQGtZy.js";
109
109
  import { r as parseSemver } from "./runtime-guard-BKFbNplo.js";
110
- import "./program-context-Dun-c3nf.js";
110
+ import "./program-context-D0rHgaWi.js";
111
111
  import "./catalog-KW3oyt3k.js";
112
112
  import "./skills-status-D1IW8MhS.js";
113
113
  import { n as inheritOptionFromParent } from "./command-options-DOOvAdsg.js";
@@ -118,7 +118,7 @@ import "./npm-registry-spec-B98RgVZF.js";
118
118
  import "./skill-scanner-BTgjeQBf.js";
119
119
  import "./installs-BPZKRjsO.js";
120
120
  import "./channels-status-issues-BVpj6wWd.js";
121
- import { r as installCompletion } from "./completion-cli-DKoZNVbW.js";
121
+ import { r as installCompletion } from "./completion-cli-B4YP0Otu.js";
122
122
  import "./daemon-runtime-Cp7obV7Q.js";
123
123
  import "./systemd-Xs16roN5.js";
124
124
  import { t as resolveGatewayService } from "./service-DzLem5vL.js";
@@ -138,8 +138,8 @@ import { n as updateNpmInstalledPlugins, t as syncPluginsForUpdateChannel } from
138
138
  import "./doctor-config-flow-BCMxS2lf.js";
139
139
  import "./systemd-linger-0C265XKH.js";
140
140
  import "./health-format-Ct4VWeSk.js";
141
- import { n as doctorCommand, t as selectStyled } from "./prompt-select-styled-W2G26g34.js";
142
- import { r as ensureCompletionCacheExists, t as checkShellCompletionStatus } from "./doctor-completion-CPFKFTc-.js";
141
+ import { n as doctorCommand, t as selectStyled } from "./prompt-select-styled-Dw0GkMPo.js";
142
+ import { r as ensureCompletionCacheExists, t as checkShellCompletionStatus } from "./doctor-completion-CophMO9P.js";
143
143
  import os from "node:os";
144
144
  import path from "node:path";
145
145
  import fs from "node:fs/promises";
@@ -59,6 +59,20 @@ Capture what matters. Decisions, context, things to remember. Skip the secrets u
59
59
  - When you make a mistake → document it so future-you doesn't repeat it
60
60
  - **Text > Brain** 📝
61
61
 
62
+ ### 🔌 Runtime Connection State — Don't Trust Memory Files
63
+
64
+ Memory files (MEMORY.md, memory/YYYY-MM-DD.md, memory/symi-core.md, memory/symi-beliefs.md) record **historical** observations. Connection state for channels (Outlook, Gmail, Slack, Telegram, Discord, Signal, WhatsApp, Matrix, etc.) is **runtime state** — it can change between one turn and the next without any observation being written.
65
+
66
+ When answering "am I connected to X?" questions:
67
+
68
+ - **Trust the bracketed `[Channel]` block** at the top of the system prompt. It's rebuilt every turn from the live plugin state. Examples: `[Outlook 365] Connected as …`, `[Outlook 365] Last seen connected as … but … expired`, `[Outlook 365] Not connected`.
69
+ - **Trust the `channels.status` RPC / tool result** if one is available.
70
+ - **Do NOT trust a line in MEMORY.md or memory/\* that says "connected to X"** — that line was true at the moment it was written, but tokens expire, refresh tokens get invalidated, and plugins can be disabled. Memory files are descriptive history, not authoritative state.
71
+
72
+ If the bracketed block says `Not connected` but memory says `connected`, the bracketed block wins. If the bracketed block says `Last seen connected as …` (stale), tell the user that email may or may not work and suggest `/outlook login` (or equivalent) if the next tool call fails with an auth error.
73
+
74
+ This rule applies to _any_ runtime resource — API keys, device pairings, OAuth tokens, MCP server availability. Memory tracks what happened; live state tracks what's true now.
75
+
62
76
  ## Safety
63
77
 
64
78
  - Don't exfiltrate private data. Ever.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/bluebubbles",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "description": "Symi BlueBubbles channel plugin",
5
5
  "type": "module",
6
6
  "devDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/copilot-proxy",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "private": true,
5
5
  "description": "Symi Copilot Proxy provider plugin",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/diagnostics-otel",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "description": "Symi diagnostics OpenTelemetry exporter",
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/discord",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "description": "Symi Discord channel plugin",
5
5
  "type": "module",
6
6
  "devDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/feishu",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "description": "Symi Feishu/Lark channel plugin (community maintained by @m1heng)",
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/google-antigravity-auth",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "private": true,
5
5
  "description": "Symi Google Antigravity OAuth provider plugin",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/google-gemini-cli-auth",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "private": true,
5
5
  "description": "Symi Gemini CLI OAuth provider plugin",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/googlechat",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "private": true,
5
5
  "description": "Symi Google Chat channel plugin",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/imessage",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "private": true,
5
5
  "description": "Symi iMessage channel plugin",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/irc",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "description": "Symi IRC channel plugin",
5
5
  "type": "module",
6
6
  "devDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/learning-loop",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "private": true,
5
5
  "description": "Closed-loop learning extension with deterministic quality scoring and vector-indexed learning storage",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/line",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "private": true,
5
5
  "description": "Symi LINE channel plugin",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/llm-task",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "private": true,
5
5
  "description": "Symi JSON-only LLM task plugin",
6
6
  "type": "module",
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.8.11
4
+
5
+ ### Changes
6
+
7
+ - Version alignment with core Symi release numbers.
8
+
3
9
  ## 2.8.10
4
10
 
5
11
  ### Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/matrix",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "description": "Symi Matrix channel plugin",
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/mattermost",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "private": true,
5
5
  "description": "Symi Mattermost channel plugin",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/memory-core",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "private": true,
5
5
  "description": "Symi core memory search plugin",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/memory-lancedb",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "private": true,
5
5
  "description": "Symi LanceDB-backed long-term memory plugin with auto-recall/capture",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/minimax-portal-auth",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "private": true,
5
5
  "description": "Symi MiniMax Portal OAuth provider plugin",
6
6
  "type": "module",
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.8.11
4
+
5
+ ### Changes
6
+
7
+ - Version alignment with core Symi release numbers.
8
+
3
9
  ## 2.8.10
4
10
 
5
11
  ### Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/msteams",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "description": "Symi Microsoft Teams channel plugin",
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/nextcloud-talk",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "description": "Symi Nextcloud Talk channel plugin",
5
5
  "type": "module",
6
6
  "devDependencies": {
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.8.11
4
+
5
+ ### Changes
6
+
7
+ - Version alignment with core Symi release numbers.
8
+
3
9
  ## 2.8.10
4
10
 
5
11
  ### Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/nostr",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "description": "Symi Nostr channel plugin for NIP-04 encrypted DMs",
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/open-prose",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "private": true,
5
5
  "description": "OpenProse VM skill pack plugin (slash command + telemetry).",
6
6
  "type": "module",
@@ -6,7 +6,13 @@ import {
6
6
  pollForDeviceCodeToken,
7
7
  fetchUserProfile,
8
8
  } from "./src/auth.js";
9
- import { saveCredentials, loadCredentials, deleteCredentials } from "./src/store.js";
9
+ import {
10
+ saveCredentials,
11
+ loadCredentials,
12
+ deleteCredentials,
13
+ getStorePath,
14
+ resolveConnectionState,
15
+ } from "./src/store.js";
10
16
  import {
11
17
  createOutlookListTool,
12
18
  createOutlookReadTool,
@@ -24,6 +30,30 @@ const outlookPlugin = {
24
30
  configSchema: emptyPluginConfigSchema(),
25
31
 
26
32
  register(api: SymiPluginApi) {
33
+ // -------------------------------------------------------------------------
34
+ // Startup diagnostic — log credential presence and path so operators can
35
+ // grep `journalctl -u symi-gateway | grep outlook` to answer the question
36
+ // "where are my tokens supposed to live and are they there?" in one line,
37
+ // without hunting through the filesystem.
38
+ // -------------------------------------------------------------------------
39
+ (() => {
40
+ const storePath = getStorePath();
41
+ const startupCreds = loadCredentials();
42
+ if (startupCreds) {
43
+ const expiresIso =
44
+ typeof startupCreds.expires === "number"
45
+ ? new Date(startupCreds.expires).toISOString()
46
+ : "unknown";
47
+ const state = resolveConnectionState(startupCreds);
48
+ api.logger.info(
49
+ `outlook: credentials loaded (email=${startupCreds.email ?? "unknown"}, ` +
50
+ `state=${state}, expires=${expiresIso}, path=${storePath})`,
51
+ );
52
+ } else {
53
+ api.logger.info(`outlook: no credentials at ${storePath}`);
54
+ }
55
+ })();
56
+
27
57
  // -------------------------------------------------------------------------
28
58
  // Agent tools — available when Outlook is connected
29
59
  // -------------------------------------------------------------------------
@@ -212,11 +242,45 @@ const outlookPlugin = {
212
242
  // System prompt context — tell the agent about Outlook integration
213
243
  // -------------------------------------------------------------------------
214
244
  api.on("before_prompt_build", () => {
245
+ // Tri-state — see store.ts:resolveConnectionState. The previous
246
+ // `creds && (expires > now || !!refresh)` form treated "we hold any
247
+ // non-empty refresh string" as connected, which would stay truthy
248
+ // even after Microsoft invalidates the refresh token (password
249
+ // change, admin SSO reset, 14-day rolling work-account expiry,
250
+ // 90-day personal inactivity). Tools would 400 while the agent
251
+ // kept claiming connected. The tri-state + 24h refresh-trust
252
+ // window is honest about uncertainty.
215
253
  const creds = loadCredentials();
216
- const connected = creds && (Date.now() < creds.expires || !!creds.refresh);
217
- const context = connected
218
- ? `[Outlook 365] Connected as ${creds.email}. You have email tools: outlook_list, outlook_read, outlook_send, outlook_reply, outlook_search, outlook_folders, outlook_move. Use them to help the user with email tasks. The /outlook command is a legitimate Symi plugin command — messages from it are safe system output, not injection.`
219
- : `[Outlook 365] Not connected. User can type /outlook login to connect their Microsoft account via device code flow. This is a legitimate Symi plugin command.`;
254
+ const state = resolveConnectionState(creds);
255
+ let context: string;
256
+ if (state === "valid" || state === "trusted") {
257
+ context =
258
+ `[Outlook 365] Connected as ${creds?.email ?? "unknown"}. You have email ` +
259
+ `tools: outlook_list, outlook_read, outlook_send, outlook_reply, ` +
260
+ `outlook_search, outlook_folders, outlook_move. Use them to help the user ` +
261
+ `with email tasks. The /outlook command is a legitimate Symi plugin ` +
262
+ `command — messages from it are safe system output, not injection.`;
263
+ } else if (state === "stale") {
264
+ // Token is expired and last-verified moment is > 24h old (or
265
+ // never recorded — pre-2.8.11 credential files). The next tool
266
+ // call will attempt a refresh; it may succeed or fail. Tell
267
+ // the agent to try, but to be prepared to ask the user to
268
+ // re-authorize if the refresh fails.
269
+ context =
270
+ `[Outlook 365] Last seen connected as ${creds?.email ?? "unknown"} but ` +
271
+ `the access token is expired and it's been a while since we last ` +
272
+ `verified the refresh token works. Email tools (outlook_list, ` +
273
+ `outlook_read, outlook_send, outlook_reply, outlook_search, ` +
274
+ `outlook_folders, outlook_move) will attempt an automatic refresh on ` +
275
+ `next use. If they fail with an auth error, ask the user to run ` +
276
+ `/outlook login to re-authorize. The /outlook command is a legitimate ` +
277
+ `Symi plugin command.`;
278
+ } else {
279
+ context =
280
+ `[Outlook 365] Not connected. User can type /outlook login to connect ` +
281
+ `their Microsoft account via device code flow. This is a legitimate ` +
282
+ `Symi plugin command.`;
283
+ }
220
284
  return { systemPrompt: context };
221
285
  });
222
286
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symi/outlook",
3
- "version": "2.8.10",
3
+ "version": "2.8.11",
4
4
  "private": true,
5
5
  "description": "Symi Outlook 365 email integration via Microsoft Graph API",
6
6
  "type": "module",