@vibesdotdev/session 1.0.0-rc.4 → 1.0.0-rc.8

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 (127) hide show
  1. package/dist/admission/admit-caller.d.ts.map +1 -1
  2. package/dist/admission/admit-caller.js +8 -3
  3. package/dist/admission/admit-caller.js.map +1 -1
  4. package/dist/cli/chat/session.repair-slugs.descriptor.d.ts +4 -0
  5. package/dist/cli/chat/session.repair-slugs.descriptor.d.ts.map +1 -0
  6. package/dist/cli/chat/session.repair-slugs.descriptor.js +16 -0
  7. package/dist/cli/chat/session.repair-slugs.descriptor.js.map +1 -0
  8. package/dist/cli/chat/session.repair-slugs.impl.d.ts +6 -0
  9. package/dist/cli/chat/session.repair-slugs.impl.d.ts.map +1 -0
  10. package/dist/cli/chat/session.repair-slugs.impl.js +25 -0
  11. package/dist/cli/chat/session.repair-slugs.impl.js.map +1 -0
  12. package/dist/cli/command.d.ts +1 -1
  13. package/dist/cli/command.d.ts.map +1 -1
  14. package/dist/cli/command.js.map +1 -1
  15. package/dist/cli/schemas/commands.d.ts +21 -0
  16. package/dist/cli/schemas/commands.d.ts.map +1 -1
  17. package/dist/cli/schemas/commands.js +10 -0
  18. package/dist/cli/schemas/commands.js.map +1 -1
  19. package/dist/cli/services/session/participants.d.ts +7 -1
  20. package/dist/cli/services/session/participants.d.ts.map +1 -1
  21. package/dist/cli/services/session/participants.js +6 -0
  22. package/dist/cli/services/session/participants.js.map +1 -1
  23. package/dist/cli/services/session.cli.consumer.d.ts +1 -1
  24. package/dist/cli/services/session.cli.consumer.d.ts.map +1 -1
  25. package/dist/cli/services/session.cli.consumer.js +1 -1
  26. package/dist/cli/services/session.cli.consumer.js.map +1 -1
  27. package/dist/cli/session-cli.plugin.d.ts.map +1 -1
  28. package/dist/cli/session-cli.plugin.js +3 -0
  29. package/dist/cli/session-cli.plugin.js.map +1 -1
  30. package/dist/daemon/commands/holds.d.ts +12 -0
  31. package/dist/daemon/commands/holds.d.ts.map +1 -0
  32. package/dist/daemon/commands/holds.js +27 -0
  33. package/dist/daemon/commands/holds.js.map +1 -0
  34. package/dist/docs/session.docs.descriptor.d.ts.map +1 -1
  35. package/dist/docs/session.docs.descriptor.js +20 -7
  36. package/dist/docs/session.docs.descriptor.js.map +1 -1
  37. package/dist/index/session-index.context.descriptor.d.ts +33 -0
  38. package/dist/index/session-index.context.descriptor.d.ts.map +1 -0
  39. package/dist/index/session-index.context.descriptor.js +11 -0
  40. package/dist/index/session-index.context.descriptor.js.map +1 -0
  41. package/dist/index/session-index.context.impl.consumer.d.ts +8 -0
  42. package/dist/index/session-index.context.impl.consumer.d.ts.map +1 -0
  43. package/dist/index/session-index.context.impl.consumer.js +7 -0
  44. package/dist/index/session-index.context.impl.consumer.js.map +1 -0
  45. package/dist/index/session-index.d.ts +29 -0
  46. package/dist/index/session-index.d.ts.map +1 -0
  47. package/dist/index/session-index.js +68 -0
  48. package/dist/index/session-index.js.map +1 -0
  49. package/dist/index/session-index.record.descriptor.d.ts +49 -0
  50. package/dist/index/session-index.record.descriptor.d.ts.map +1 -0
  51. package/dist/index/session-index.record.descriptor.js +59 -0
  52. package/dist/index/session-index.record.descriptor.js.map +1 -0
  53. package/dist/manager.d.ts +22 -1
  54. package/dist/manager.d.ts.map +1 -1
  55. package/dist/manager.js +77 -45
  56. package/dist/manager.js.map +1 -1
  57. package/dist/mcp/utils/participant.d.ts +18 -0
  58. package/dist/mcp/utils/participant.d.ts.map +1 -1
  59. package/dist/mcp/utils/participant.js +97 -6
  60. package/dist/mcp/utils/participant.js.map +1 -1
  61. package/dist/schemas/chatter.d.ts +2 -0
  62. package/dist/schemas/chatter.d.ts.map +1 -1
  63. package/dist/schemas/chatter.js +4 -0
  64. package/dist/schemas/chatter.js.map +1 -1
  65. package/dist/schemas/participant.d.ts +3 -0
  66. package/dist/schemas/participant.d.ts.map +1 -1
  67. package/dist/schemas/participant.js +10 -0
  68. package/dist/schemas/participant.js.map +1 -1
  69. package/dist/session.consumer.plugin.d.ts.map +1 -1
  70. package/dist/session.consumer.plugin.js +5 -1
  71. package/dist/session.consumer.plugin.js.map +1 -1
  72. package/dist/storage/session-connections.record.descriptor.d.ts +1 -0
  73. package/dist/storage/session-connections.record.descriptor.d.ts.map +1 -1
  74. package/dist/storage/session-messages.record.descriptor.d.ts +1 -0
  75. package/dist/storage/session-messages.record.descriptor.d.ts.map +1 -1
  76. package/dist/storage/write-or-recover.d.ts +21 -0
  77. package/dist/storage/write-or-recover.d.ts.map +1 -0
  78. package/dist/storage/write-or-recover.js +46 -0
  79. package/dist/storage/write-or-recover.js.map +1 -0
  80. package/dist/tools/lifecycle/init/schemas.d.ts.map +1 -1
  81. package/dist/tools/lifecycle/init/schemas.js +5 -2
  82. package/dist/tools/lifecycle/init/schemas.js.map +1 -1
  83. package/dist/tools/lifecycle/snapshot/handler.d.ts +1 -0
  84. package/dist/tools/lifecycle/snapshot/handler.d.ts.map +1 -1
  85. package/dist/tools/lifecycle/tool/session.tool.descriptor.d.ts.map +1 -1
  86. package/dist/tools/lifecycle/tool/session.tool.descriptor.js +3 -2
  87. package/dist/tools/lifecycle/tool/session.tool.descriptor.js.map +1 -1
  88. package/dist/tools/messages/handlers.d.ts.map +1 -1
  89. package/dist/tools/messages/handlers.js +2 -0
  90. package/dist/tools/messages/handlers.js.map +1 -1
  91. package/dist/tools/messages/tool/session-collaborate.descriptor.d.ts.map +1 -1
  92. package/dist/tools/messages/tool/session-collaborate.descriptor.js +3 -2
  93. package/dist/tools/messages/tool/session-collaborate.descriptor.js.map +1 -1
  94. package/dist/tools/schema-descriptions.d.ts +16 -0
  95. package/dist/tools/schema-descriptions.d.ts.map +1 -0
  96. package/dist/tools/schema-descriptions.js +94 -0
  97. package/dist/tools/schema-descriptions.js.map +1 -0
  98. package/dist/tools/scratchpad/tool/session-scratchpad.descriptor.d.ts.map +1 -1
  99. package/dist/tools/scratchpad/tool/session-scratchpad.descriptor.js +3 -2
  100. package/dist/tools/scratchpad/tool/session-scratchpad.descriptor.js.map +1 -1
  101. package/dist/tools/todos/list/handler.js +1 -1
  102. package/dist/tools/todos/list/handler.js.map +1 -1
  103. package/dist/tools/todos/list/schemas.d.ts +1 -0
  104. package/dist/tools/todos/list/schemas.d.ts.map +1 -1
  105. package/dist/tools/todos/list/schemas.js +5 -0
  106. package/dist/tools/todos/list/schemas.js.map +1 -1
  107. package/dist/tools/todos/tool/schemas/index.d.ts +1 -0
  108. package/dist/tools/todos/tool/schemas/index.d.ts.map +1 -1
  109. package/dist/tools/todos/tool/session-todo.descriptor.d.ts.map +1 -1
  110. package/dist/tools/todos/tool/session-todo.descriptor.js +3 -2
  111. package/dist/tools/todos/tool/session-todo.descriptor.js.map +1 -1
  112. package/dist/tools/todos/tool/session-todo.variant.list.impl.d.ts +1 -0
  113. package/dist/tools/todos/tool/session-todo.variant.list.impl.d.ts.map +1 -1
  114. package/dist/tools/workspace/tool/schemas/index.d.ts +8 -1
  115. package/dist/tools/workspace/tool/schemas/index.d.ts.map +1 -1
  116. package/dist/tools/workspace/tool/schemas/index.js +5 -1
  117. package/dist/tools/workspace/tool/schemas/index.js.map +1 -1
  118. package/dist/tools/workspace/tool/session-workspace.descriptor.d.ts.map +1 -1
  119. package/dist/tools/workspace/tool/session-workspace.descriptor.js +6 -3
  120. package/dist/tools/workspace/tool/session-workspace.descriptor.js.map +1 -1
  121. package/dist/tools/workspace/tool/session-workspace.variant.list.impl.d.ts +1 -0
  122. package/dist/tools/workspace/tool/session-workspace.variant.list.impl.d.ts.map +1 -1
  123. package/dist/tools/workspace/tool/session-workspace.variant.pair.impl.d.ts +1 -0
  124. package/dist/tools/workspace/tool/session-workspace.variant.pair.impl.d.ts.map +1 -1
  125. package/dist/tools/workspace/tool/session-workspace.variant.unpair.impl.d.ts +1 -0
  126. package/dist/tools/workspace/tool/session-workspace.variant.unpair.impl.d.ts.map +1 -1
  127. package/package.json +52 -19
package/dist/manager.js CHANGED
@@ -18,7 +18,7 @@ import { getVibesClient } from '@vibesdotdev/kernel/client';
18
18
  import { JsonValueSchema as JsonSchema } from '@vibesdotdev/kernel/schemas/json';
19
19
  import { getLogger } from '@vibesdotdev/logging';
20
20
  import { StorageRecord } from '@vibesdotdev/storage/record';
21
- import { storageFailureCondition } from '@vibesdotdev/storage/record/failure-condition';
21
+ import { isUniqueConstraintError, writeOrRecover } from "./storage/write-or-recover.js";
22
22
  import { ScratchpadContentTypeSchema } from "./schemas/index.js";
23
23
  import { HarnessSeatRegistrationSchema, HarnessSeatSchema, harnessSeatParticipantId, harnessSeatSlug } from "./schemas/harness-seat.js";
24
24
  import { ensureSessionContextWithManager, getOrCreateMCPParticipant, resolveParticipantReference, participantSlugOf, sanitizeSlugCandidate, scanParticipantsBySlug } from "./mcp/utils/participant.js";
@@ -31,44 +31,6 @@ const SESSION_STATE_MCP_PREFIX = 'mcp:';
31
31
  const PARTICIPANT_ACTIVE_WINDOW_MS = 5 * 60 * 1000;
32
32
  /** The runtime client id notifications register their delivery client under. */
33
33
  const NOTIFICATIONS_CLIENT_ID = 'notifications.delivery';
34
- /**
35
- * Whether a storage write lost a unique-index race. LocalDB's driver classifies
36
- * the collision on its typed refusal (`unique-constraint`); the adapter's text
37
- * is never read. A conflict another adapter reports untyped is not recognised
38
- * here and propagates (cross-lane: storage has no adapter-neutral conflict code).
39
- */
40
- function isUniqueConstraintError(failure) {
41
- return storageFailureCondition(failure) === 'unique-constraint';
42
- }
43
- /**
44
- * Run `write`; when it fails with a failure `recoverable` admits, run
45
- * `recover` instead. Any other failure of `write`, and a recovery that finds
46
- * nothing (`null`), rethrows the write's own failure; a recovery that throws
47
- * carries its own failure. The write's failure is never dropped.
48
- */
49
- async function writeOrRecover(write, recover, recoverable) {
50
- let lost;
51
- const steps = [
52
- write,
53
- async () => {
54
- if (!recoverable(CaughtFailureSchema.parse(lost)))
55
- throw lost;
56
- const recovered = await recover();
57
- if (recovered === null)
58
- throw lost;
59
- return recovered;
60
- }
61
- ];
62
- for (const step of steps) {
63
- try {
64
- return await step();
65
- }
66
- catch (error) {
67
- lost = error;
68
- }
69
- }
70
- throw lost;
71
- }
72
34
  /** Any write failure admits a recovery read (the recovery itself confirms the race). */
73
35
  function anyFailure() {
74
36
  return true;
@@ -1870,8 +1832,14 @@ export class SessionManager {
1870
1832
  });
1871
1833
  }
1872
1834
  }
1873
- const kept = existing && SessionManager.isLiveInstance(existing, nowIso) ? existing.activeHandle ?? undefined : undefined;
1835
+ // The handle this key's own row still holds, live or lapsed: handles are
1836
+ // unique, so a lapsed row that still holds one holds it for no one else.
1837
+ const kept = existing && !existing.disconnectedAt ? existing.activeHandle ?? undefined : undefined;
1874
1838
  const base = input.agentId ?? input.harness ?? input.clientName;
1839
+ const preferred = chosen ? undefined : await this.usablePreferredHandle(input, existing);
1840
+ // A conversation arriving under a new key (its id changed) keeps the handle
1841
+ // it holds under the old one: one conversation is one seat.
1842
+ const inherited = chosen || kept ? undefined : await this.conversationHandle(input, nowIso);
1875
1843
  const fields = {
1876
1844
  participantId: input.participantId ?? existing?.participantId,
1877
1845
  clientName: input.clientName,
@@ -1883,14 +1851,23 @@ export class SessionManager {
1883
1851
  agentId: input.agentId,
1884
1852
  harness: input.harness,
1885
1853
  harnessSessionId: input.harnessSessionId,
1854
+ homeWorkspace: input.homeWorkspace ?? existing?.homeWorkspace ?? null,
1886
1855
  lastHeartbeatAt: nowAt,
1887
1856
  expiresAt,
1888
1857
  disconnectedAt: null
1889
1858
  };
1859
+ // The handle to try, in order: the one demanded; else the one preferred (the
1860
+ // seat's home handle), then the one already held, then a random one.
1861
+ const candidates = chosen ? [] : [preferred, kept ?? inherited].filter((handle) => !!handle);
1890
1862
  for (let attempt = 0; attempt < SessionManager.INSTANCE_HANDLE_ATTEMPTS; attempt++) {
1891
- const handle = chosen ?? (attempt === 0 && kept ? kept : SessionManager.randomHandle(base));
1892
- if (handle !== existing?.activeHandle)
1893
- await this.releaseExpiredHandle(handle, input.instanceId, nowIso);
1863
+ const handle = chosen ?? candidates[attempt] ?? SessionManager.randomHandle(base);
1864
+ if (handle !== existing?.activeHandle) {
1865
+ await this.releaseExpiredHandle(handle, input.instanceId, nowIso, {
1866
+ participantId: fields.participantId,
1867
+ harnessSessionId: input.harnessSessionId,
1868
+ transport: input.transport
1869
+ });
1870
+ }
1894
1871
  const row = { ...fields, handle, activeHandle: handle };
1895
1872
  const written = await settle(() => existing
1896
1873
  ? storage.update(input.instanceId, row)
@@ -1919,8 +1896,50 @@ export class SessionManager {
1919
1896
  reason: 'instance-handle-exhausted'
1920
1897
  });
1921
1898
  }
1922
- /** Free `handle` when its holder is no longer live, so a new instance can take it. */
1923
- async releaseExpiredHandle(handle, claimant, nowIso) {
1899
+ /** The handle `input`'s own conversation holds under another instance key it succeeds, if any. */
1900
+ async conversationHandle(input, nowIso) {
1901
+ if (!input.harnessSessionId || !input.participantId)
1902
+ return undefined;
1903
+ const rows = await this.connectionsStorage.list({
1904
+ where: { harnessSessionId: input.harnessSessionId, participantId: input.participantId },
1905
+ limit: 10
1906
+ });
1907
+ const sibling = rows.items.find((row) => row.connectionId !== input.instanceId &&
1908
+ !!row.activeHandle &&
1909
+ !row.disconnectedAt &&
1910
+ SessionManager.succeeds(row, input.transport, nowIso));
1911
+ return sibling?.activeHandle ?? undefined;
1912
+ }
1913
+ /**
1914
+ * Whether a new key of the same conversation succeeds `row`: the row has
1915
+ * lapsed, or it is on the same transport (a restart). A conversation live
1916
+ * on two transports at once (an MCP connection and an in-process host) is
1917
+ * two instances, and neither takes the other's handle or closes it.
1918
+ */
1919
+ static succeeds(row, transport, nowIso) {
1920
+ return !SessionManager.isLiveInstance(row, nowIso) || (!!transport && row.transport === transport);
1921
+ }
1922
+ /**
1923
+ * The preferred handle an instance may take: sanitized, and not another
1924
+ * participant's name (a live handle resolves ahead of slugs, so it would
1925
+ * capture that participant's messages). Undefined when there is none.
1926
+ */
1927
+ async usablePreferredHandle(input, existing) {
1928
+ const preferred = input.preferredHandle?.trim() ? sanitizeSlugCandidate(input.preferredHandle) : undefined;
1929
+ if (!preferred)
1930
+ return undefined;
1931
+ const named = (await scanParticipantsBySlug(this, preferred)).match;
1932
+ const actsAs = input.participantId ?? existing?.participantId;
1933
+ return named && named.participantId !== actsAs ? undefined : preferred;
1934
+ }
1935
+ /**
1936
+ * Free `handle` for `claimant` when its holder is no longer live, or when the
1937
+ * holder is the claimant's own conversation under another instance key the
1938
+ * claimant succeeds (same participant, same harness conversation; lapsed or on
1939
+ * the same transport): one conversation is one seat, so the stale key is
1940
+ * closed and the handle moves with it.
1941
+ */
1942
+ async releaseExpiredHandle(handle, claimant, nowIso, seat = {}) {
1924
1943
  const held = await this.connectionsStorage.list({
1925
1944
  where: { activeHandle: handle },
1926
1945
  limit: 1
@@ -1928,6 +1947,14 @@ export class SessionManager {
1928
1947
  const holder = held.items[0];
1929
1948
  if (!holder || holder.connectionId === claimant)
1930
1949
  return;
1950
+ const sameConversation = !!seat.harnessSessionId &&
1951
+ holder.harnessSessionId === seat.harnessSessionId &&
1952
+ !!seat.participantId &&
1953
+ holder.participantId === seat.participantId;
1954
+ if (sameConversation && SessionManager.succeeds(holder, seat.transport, nowIso)) {
1955
+ await this.closeInstance(holder.connectionId);
1956
+ return;
1957
+ }
1931
1958
  if (SessionManager.isLiveInstance(holder, nowIso))
1932
1959
  return;
1933
1960
  await this.connectionsStorage.update(holder.connectionId, { activeHandle: null });
@@ -2170,6 +2197,11 @@ export class SessionManager {
2170
2197
  });
2171
2198
  for (const message of arrived.items) {
2172
2199
  const createdAt = message.createdAt instanceof Date ? message.createdAt : new Date(message.createdAt);
2200
+ // A message the sender addressed to one instance (by its handle) wakes only that instance.
2201
+ if (message.toInstance && message.toInstance !== instanceId) {
2202
+ this.deliveryCursors.set(instanceId, createdAt);
2203
+ continue;
2204
+ }
2173
2205
  const sender = await this.getParticipant(message.fromParticipant);
2174
2206
  await this.hostRuntime.hooks('session').emit('messageArrived', {
2175
2207
  instanceId,