@gethmy/mcp 3.4.1 → 3.5.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.
package/dist/cli.js CHANGED
@@ -5693,7 +5693,7 @@ var TOOLS = {
5693
5693
  }
5694
5694
  },
5695
5695
  harmony_update_agent_progress: {
5696
- description: "Update progress on an active agent session. Use to report progress percentage, current task, blockers, or status changes. Check the reply: `session: null` with `stopped: true` means a human stopped this card's run, so reporting progress will no longer open a session here (card #770) — stop work if you were mid-run, and read `recovery` for the one legitimate way back. Otherwise a session is always returned: this call opens one if none is live.",
5696
+ description: "Update progress on an active agent session. Use to report progress percentage, current task, blockers, or status changes. Check the reply: `session: null` with `stopped: true` means a human stopped this card's run, so reporting progress will no longer open a session here (card #770) — stop work if you were mid-run, and read `recovery` for the one legitimate way back. Otherwise a session is always returned: this call opens one if none is live. `sessionStale: true` means it opened one IN PLACE OF a session the inactivity sweep had closed (card #1067): nobody stopped you and the new row keeps the old one's steering channel and driver, so carry on — but the id changed, so poll harmony_get_pending_messages with the new `session.id` from this reply, not the one you were holding. `liveSessionIdChanged: true` is the weaker cousin: the live session is simply not the one you started, so use the new id, but do NOT assume steering survived — read `instruction`.",
5697
5697
  inputSchema: {
5698
5698
  type: "object",
5699
5699
  properties: {
@@ -7654,6 +7654,32 @@ ${options}
7654
7654
  if (result.session === null) {
7655
7655
  untrack(cardId, deps.getScopeId?.());
7656
7656
  }
7657
+ const scopeId = deps.getScopeId?.();
7658
+ const ownSession = memSession && memSession.scopeId === scopeId ? memSession : undefined;
7659
+ const knownSessionId = ownSession?.agentSessionId;
7660
+ const liveSessionId = result.session?.id;
7661
+ const idChangedUnderUs = knownSessionId !== undefined && liveSessionId !== undefined && liveSessionId !== knownSessionId;
7662
+ if (idChangedUnderUs && ownSession && liveSessionId) {
7663
+ ownSession.agentSessionId = liveSessionId;
7664
+ }
7665
+ const newSessionId = liveSessionId ? ` (${liveSessionId})` : "";
7666
+ if (result.sessionStale) {
7667
+ return {
7668
+ success: true,
7669
+ midSessionLearnings: 0,
7670
+ ...result,
7671
+ instruction: "Your previous session had gone stale, so this report opened a new one in its place — nobody stopped you, and it kept the steering channel and driver of the run it replaced. Do NOT abandon the work. Poll harmony_get_pending_messages with the NEW session id" + newSessionId + ", not the one you were holding."
7672
+ };
7673
+ }
7674
+ if (idChangedUnderUs) {
7675
+ return {
7676
+ success: true,
7677
+ midSessionLearnings: 0,
7678
+ ...result,
7679
+ liveSessionIdChanged: true,
7680
+ instruction: "The live session on this card is no longer the one you started — it may have been reopened, or deliberately ended and replaced. Nobody stopped you, so carry on, but poll harmony_get_pending_messages with the session id from this reply" + newSessionId + ". If steering matters to this run, do not assume it survived: start a session deliberately with harmony_start_agent_session and steerable: true."
7681
+ };
7682
+ }
7657
7683
  return { success: true, midSessionLearnings: 0, ...result };
7658
7684
  }
7659
7685
  case "harmony_end_agent_session": {
package/dist/index.js CHANGED
@@ -5476,7 +5476,7 @@ var TOOLS = {
5476
5476
  }
5477
5477
  },
5478
5478
  harmony_update_agent_progress: {
5479
- description: "Update progress on an active agent session. Use to report progress percentage, current task, blockers, or status changes. Check the reply: `session: null` with `stopped: true` means a human stopped this card's run, so reporting progress will no longer open a session here (card #770) — stop work if you were mid-run, and read `recovery` for the one legitimate way back. Otherwise a session is always returned: this call opens one if none is live.",
5479
+ description: "Update progress on an active agent session. Use to report progress percentage, current task, blockers, or status changes. Check the reply: `session: null` with `stopped: true` means a human stopped this card's run, so reporting progress will no longer open a session here (card #770) — stop work if you were mid-run, and read `recovery` for the one legitimate way back. Otherwise a session is always returned: this call opens one if none is live. `sessionStale: true` means it opened one IN PLACE OF a session the inactivity sweep had closed (card #1067): nobody stopped you and the new row keeps the old one's steering channel and driver, so carry on — but the id changed, so poll harmony_get_pending_messages with the new `session.id` from this reply, not the one you were holding. `liveSessionIdChanged: true` is the weaker cousin: the live session is simply not the one you started, so use the new id, but do NOT assume steering survived — read `instruction`.",
5480
5480
  inputSchema: {
5481
5481
  type: "object",
5482
5482
  properties: {
@@ -7437,6 +7437,32 @@ ${options}
7437
7437
  if (result.session === null) {
7438
7438
  untrack(cardId, deps.getScopeId?.());
7439
7439
  }
7440
+ const scopeId = deps.getScopeId?.();
7441
+ const ownSession = memSession && memSession.scopeId === scopeId ? memSession : undefined;
7442
+ const knownSessionId = ownSession?.agentSessionId;
7443
+ const liveSessionId = result.session?.id;
7444
+ const idChangedUnderUs = knownSessionId !== undefined && liveSessionId !== undefined && liveSessionId !== knownSessionId;
7445
+ if (idChangedUnderUs && ownSession && liveSessionId) {
7446
+ ownSession.agentSessionId = liveSessionId;
7447
+ }
7448
+ const newSessionId = liveSessionId ? ` (${liveSessionId})` : "";
7449
+ if (result.sessionStale) {
7450
+ return {
7451
+ success: true,
7452
+ midSessionLearnings: 0,
7453
+ ...result,
7454
+ instruction: "Your previous session had gone stale, so this report opened a new one in its place — nobody stopped you, and it kept the steering channel and driver of the run it replaced. Do NOT abandon the work. Poll harmony_get_pending_messages with the NEW session id" + newSessionId + ", not the one you were holding."
7455
+ };
7456
+ }
7457
+ if (idChangedUnderUs) {
7458
+ return {
7459
+ success: true,
7460
+ midSessionLearnings: 0,
7461
+ ...result,
7462
+ liveSessionIdChanged: true,
7463
+ instruction: "The live session on this card is no longer the one you started — it may have been reopened, or deliberately ended and replaced. Nobody stopped you, so carry on, but poll harmony_get_pending_messages with the session id from this reply" + newSessionId + ". If steering matters to this run, do not assume it survived: start a session deliberately with harmony_start_agent_session and steerable: true."
7464
+ };
7465
+ }
7440
7466
  return { success: true, midSessionLearnings: 0, ...result };
7441
7467
  }
7442
7468
  case "harmony_end_agent_session": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gethmy/mcp",
3
- "version": "3.4.1",
3
+ "version": "3.5.0",
4
4
  "description": "MCP server for Harmony, the shared surface for human–agent teams — agents claim cards, report progress, and move work on your board.",
5
5
  "publishConfig": {
6
6
  "access": "public"
package/src/api-client.ts CHANGED
@@ -1335,6 +1335,23 @@ export class HarmonyApiClient {
1335
1335
  stopReason?: "human_stopped";
1336
1336
  /** How to legitimately work this card again (an explicit start). */
1337
1337
  recovery?: string;
1338
+ /**
1339
+ * This write REPLACED a session the silence sweep had closed (#1067): the
1340
+ * returned session is a new row under a new id, carrying forward the
1341
+ * `steerable`/`driver` of the one it replaced. Not a stop — the run
1342
+ * continues — but every later poll must use the NEW `session.id`.
1343
+ *
1344
+ * Same word `getPendingMessages` answers with, deliberately: one flag name
1345
+ * for one fact across both calls. Absent on an ordinary update, on a
1346
+ * deliberate start, and from a pre-#1067 server.
1347
+ */
1348
+ sessionStale?: boolean;
1349
+ /**
1350
+ * The swept session that was replaced — never the new one's id. Present
1351
+ * exactly when `sessionStale` is, so it is also the discriminator between
1352
+ * the server's own verdict and the MCP tool's weaker `liveSessionIdChanged`.
1353
+ */
1354
+ replacedSessionId?: string;
1338
1355
  }> {
1339
1356
  return this.request("POST", `/cards/${cardId}/agent-context`, data);
1340
1357
  }
package/src/server.ts CHANGED
@@ -1610,7 +1610,7 @@ export const TOOLS = {
1610
1610
  },
1611
1611
  harmony_update_agent_progress: {
1612
1612
  description:
1613
- "Update progress on an active agent session. Use to report progress percentage, current task, blockers, or status changes. Check the reply: `session: null` with `stopped: true` means a human stopped this card's run, so reporting progress will no longer open a session here (card #770) — stop work if you were mid-run, and read `recovery` for the one legitimate way back. Otherwise a session is always returned: this call opens one if none is live.",
1613
+ "Update progress on an active agent session. Use to report progress percentage, current task, blockers, or status changes. Check the reply: `session: null` with `stopped: true` means a human stopped this card's run, so reporting progress will no longer open a session here (card #770) — stop work if you were mid-run, and read `recovery` for the one legitimate way back. Otherwise a session is always returned: this call opens one if none is live. `sessionStale: true` means it opened one IN PLACE OF a session the inactivity sweep had closed (card #1067): nobody stopped you and the new row keeps the old one's steering channel and driver, so carry on — but the id changed, so poll harmony_get_pending_messages with the new `session.id` from this reply, not the one you were holding. `liveSessionIdChanged: true` is the weaker cousin: the live session is simply not the one you started, so use the new id, but do NOT assume steering survived — read `instruction`.",
1614
1614
  inputSchema: {
1615
1615
  type: "object",
1616
1616
  properties: {
@@ -4379,6 +4379,77 @@ export async function handleToolCall(
4379
4379
  untrack(cardId, deps.getScopeId?.());
4380
4380
  }
4381
4381
 
4382
+ // Two DIFFERENT facts, and the earlier cut of #1067 merged them into one
4383
+ // sentence that was false in three reachable cases.
4384
+ //
4385
+ // `result.sessionStale` is the server's own verdict: it replaced a
4386
+ // recently-swept session AND carried its `steerable`/`driver` forward. Only
4387
+ // that verdict licenses saying the steering channel survived.
4388
+ //
4389
+ // The id comparison establishes strictly less — that the live session is
4390
+ // not the one this run recorded at start. It catches the case the flag
4391
+ // cannot: `flushMemoryActions` is fire-and-forget, sends
4392
+ // `implicitCreate: true` and discards its result, so when the sweep has
4393
+ // closed the row that bookkeeping write is what mints the replacement and
4394
+ // consumes the server's one `sessionStale`; this checkpoint then lands on a
4395
+ // live row under a new id with `created: false`. But an id change alone is
4396
+ // ALSO what `harmony_move_card` leaves behind — it ends the session and
4397
+ // untracks the card without calling `cleanupMemorySession`, so the recorded
4398
+ // id outlives a deliberate `completed` end — and what a lapsed recency
4399
+ // window leaves behind, where the new row genuinely is NOT steerable.
4400
+ // Claiming inheritance there tells a run its steering survived when it
4401
+ // did not, which suppresses the one recovery that works (an explicit
4402
+ // `harmony_start_agent_session` with `steerable: true`). Worse than
4403
+ // silence, so the two paths say different things.
4404
+ //
4405
+ // The RESYNC is unconditional and is a fix in its own right: this field
4406
+ // scopes working-memory writes (`sessionScopeFor`) and feeds comment
4407
+ // attribution, so a stale id there is wrong regardless of what is reported.
4408
+ const scopeId = deps.getScopeId?.();
4409
+ // `memorySessions` is keyed by card id ALONE at module scope, so on the
4410
+ // hosted transport a lookup can hand back another user's session for the
4411
+ // same card. Anything that CLAIMS the tracked session compares the scope
4412
+ // first — `chooseCommentSession` does, and #1035 is why.
4413
+ const ownSession =
4414
+ memSession && memSession.scopeId === scopeId ? memSession : undefined;
4415
+ const knownSessionId = ownSession?.agentSessionId;
4416
+ const liveSessionId = (result.session as { id?: string } | null)?.id;
4417
+ const idChangedUnderUs =
4418
+ knownSessionId !== undefined &&
4419
+ liveSessionId !== undefined &&
4420
+ liveSessionId !== knownSessionId;
4421
+ // Report once: the run now knows this id, so a later checkpoint on the
4422
+ // same session is silent again rather than crying change forever.
4423
+ if (idChangedUnderUs && ownSession && liveSessionId) {
4424
+ ownSession.agentSessionId = liveSessionId;
4425
+ }
4426
+ const newSessionId = liveSessionId ? ` (${liveSessionId})` : "";
4427
+ if (result.sessionStale) {
4428
+ return {
4429
+ success: true,
4430
+ midSessionLearnings: 0,
4431
+ ...result,
4432
+ instruction:
4433
+ "Your previous session had gone stale, so this report opened a new one in its place — nobody stopped you, and it kept the steering channel and driver of the run it replaced. Do NOT abandon the work. Poll harmony_get_pending_messages with the NEW session id" +
4434
+ newSessionId +
4435
+ ", not the one you were holding.",
4436
+ };
4437
+ }
4438
+ if (idChangedUnderUs) {
4439
+ // Everything this branch knows, and nothing it does not: no claim that
4440
+ // anything went stale, and none that any capability was preserved.
4441
+ return {
4442
+ success: true,
4443
+ midSessionLearnings: 0,
4444
+ ...result,
4445
+ liveSessionIdChanged: true,
4446
+ instruction:
4447
+ "The live session on this card is no longer the one you started — it may have been reopened, or deliberately ended and replaced. Nobody stopped you, so carry on, but poll harmony_get_pending_messages with the session id from this reply" +
4448
+ newSessionId +
4449
+ ". If steering matters to this run, do not assume it survived: start a session deliberately with harmony_start_agent_session and steerable: true.",
4450
+ };
4451
+ }
4452
+
4382
4453
  // Phase 0 (memory architecture v2): mid-session learning extraction removed.
4383
4454
  return { success: true, midSessionLearnings: 0, ...result };
4384
4455
  }