agents-can-communicate 0.8.3 → 0.8.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.
Files changed (51) hide show
  1. package/bin/acc-update-worker.mjs +4 -1
  2. package/bin/entrypoints/acc-mcp.mjs +3 -0
  3. package/bin/entrypoints/acc.mjs +5 -0
  4. package/docs/ARCHITECTURE.md +3 -1
  5. package/docs/CLI.md +7 -1
  6. package/docs/CONCEPTS.md +2 -2
  7. package/docs/CONFIGURATION.md +3 -2
  8. package/docs/GETTING_STARTED.md +12 -9
  9. package/docs/HOW_IT_WORKS.md +2 -1
  10. package/docs/TROUBLESHOOTING.md +11 -2
  11. package/docs/UPGRADING.md +18 -0
  12. package/node_modules/@agents-can-communicate/adapter-antigravity/package.json +1 -1
  13. package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/plugin.json +1 -1
  14. package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
  15. package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
  16. package/node_modules/@agents-can-communicate/adapter-codex/src/adapter.mjs +7 -1
  17. package/node_modules/@agents-can-communicate/adapter-codex/src/app-server-client.mjs +24 -1
  18. package/node_modules/@agents-can-communicate/adapter-codex/src/embedded-host.mjs +65 -0
  19. package/node_modules/@agents-can-communicate/adapter-codex/src/install.mjs +2 -0
  20. package/node_modules/@agents-can-communicate/adapter-codex/src/maintenance-host.mjs +33 -9
  21. package/node_modules/@agents-can-communicate/adapter-codex/src/native-delivery.mjs +38 -1
  22. package/node_modules/@agents-can-communicate/adapter-codex/src/native-discovery.mjs +82 -0
  23. package/node_modules/@agents-can-communicate/adapter-codex/src/service-setup.mjs +59 -5
  24. package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
  25. package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
  26. package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
  27. package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
  28. package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
  29. package/node_modules/@agents-can-communicate/adapter-sdk/src/native-vocabulary.mjs +3 -0
  30. package/node_modules/@agents-can-communicate/cli/package.json +1 -1
  31. package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +2 -1
  32. package/node_modules/@agents-can-communicate/cli/src/main.mjs +18 -2
  33. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/activation.mjs +25 -8
  34. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/install.mjs +13 -3
  35. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/schedule.mjs +19 -1
  36. package/node_modules/@agents-can-communicate/cli/src/managed-runtime/worker.mjs +38 -3
  37. package/node_modules/@agents-can-communicate/cli/src/native-delivery-status.mjs +5 -1
  38. package/node_modules/@agents-can-communicate/cli/src/native-session-diagnostics.mjs +3 -0
  39. package/node_modules/@agents-can-communicate/core/package.json +1 -1
  40. package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
  41. package/node_modules/@agents-can-communicate/delivery-router/src/native-reason.mjs +18 -0
  42. package/node_modules/@agents-can-communicate/delivery-router/src/router.mjs +15 -3
  43. package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
  44. package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +110 -4
  45. package/node_modules/@agents-can-communicate/installer/package.json +1 -1
  46. package/node_modules/@agents-can-communicate/installer/src/delivery-diagnostics.mjs +2 -0
  47. package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
  48. package/node_modules/@agents-can-communicate/mcp-server/src/server.mjs +3 -0
  49. package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
  50. package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
  51. package/package.json +1 -1
@@ -5,12 +5,14 @@ import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
 
7
7
  import { capabilityEvidence, clearNativeAttempt, clearSessionBinding, effectiveCapabilities,
8
- loadSessionBinding, storeSessionBinding }
8
+ loadSessionBinding, normalizedEvent, storeSessionBinding }
9
9
  from "@agents-can-communicate/adapter-sdk";
10
10
  import { createCoordinationService } from "@agents-can-communicate/core";
11
+ import { loadOwnership } from "@agents-can-communicate/installer";
11
12
  import { AccError, createId } from "@agents-can-communicate/protocol";
12
13
  import { openFilesystemStore } from "@agents-can-communicate/storage-filesystem";
13
- import { clearPin, createGitProbe, resolveHookWorkspace, platformDataHome, runtimePaths, writePin }
14
+ import { clearPin, createGitProbe, resolveHookWorkspace, platformDataHome, runtimePaths,
15
+ writePin as writeRuntimePin }
14
16
  from "@agents-can-communicate/cli";
15
17
 
16
18
  import { resolveClientPid } from "./client-pid.mjs";
@@ -388,11 +390,16 @@ async function projectActivation(input, offered) {
388
390
  }
389
391
 
390
392
  const HANDLERS = {
393
+ // `knownClientPid` and `pins: false` are registration's (#167): it runs in
394
+ // a peer's process, so the client's pid is handed over rather than found in
395
+ // this process's ancestry, and it pins nothing because no hook code of any
396
+ // generation runs for the session until its first real hook pins its own.
391
397
  async sessionStart({ event, context, adapter, adapterId, binding, paths,
392
- readProcessTable, probeClientVersion, platform, deadline }) {
398
+ readProcessTable, probeClientVersion, platform, deadline, knownClientPid, pins = true }) {
393
399
  // Where this session's pin lives, resolved once and reused at every write
394
400
  // below. Beside `paths.root`, never inside it: see `managerRootFor`.
395
401
  const pinRoot = managerRootFor(context.dataHome);
402
+ const writePin = pins === false ? async () => {} : writeRuntimePin;
396
403
  // A repeated start refreshes the client's version/platform. Remove the old
397
404
  // certified facts before any probe, PID lookup, resume, or open can fail;
398
405
  // keep only the generation identity needed for a successful resume.
@@ -414,7 +421,8 @@ const HANDLERS = {
414
421
  // null, and the session is then judged by age alone - which is exactly the
415
422
  // behaviour every session had before this existed.
416
423
  const command = adapter.client?.command ?? null;
417
- const pid = command === null ? null
424
+ const pid = Number.isInteger(knownClientPid) && knownClientPid > 0 ? knownClientPid
425
+ : command === null ? null
418
426
  : resolveClientPid({ table: await readProcessTable({
419
427
  timeoutMs: Math.max(1, Math.min(1_000, deadline - Date.now())) }), from: process.pid, command });
420
428
  assertHookBudget(deadline);
@@ -767,3 +775,101 @@ export async function runHook({ adapterId, payload, adapters, dataHome, env,
767
775
  clearTimeout(timer);
768
776
  }
769
777
  }
778
+
779
+ /**
780
+ * Register a client session before its first turn (#167).
781
+ *
782
+ * Exactly what SessionStart writes - binding, session, native binding and its
783
+ * diagnostic - under the same per-session lifecycle lock, so the chat's first
784
+ * real hook finds the binding and resumes this session instead of opening a
785
+ * second one. The caller is a peer's process, so the client's pid is handed
786
+ * over, the caller's own ACC_PARTICIPANT never names the chat, and nothing is
787
+ * pinned or printed. A session that already has a binding is left alone.
788
+ * Never throws: status must not fail because of it.
789
+ */
790
+ export async function registerNativeSession({ adapterId, adapters, event: target, clientPid,
791
+ workspaceId, dataHome, env = {}, runtime = defaultRuntime(), budgetMs = DEFAULT_BUDGET_MS,
792
+ probeClientVersion = defaultProbeClientVersion, platform = `${process.platform}-${process.arch}` }) {
793
+ const deadline = Date.now() + budgetMs;
794
+ const skipped = reason => ({ registered: false, reason });
795
+ const register = async () => {
796
+ const adapter = adapters?.[adapterId];
797
+ if (adapter === undefined || !Number.isInteger(clientPid) || clientPid <= 0) return skipped("unsupported");
798
+ const event = normalizedEvent({ kind: "sessionStart", sessionId: target?.sessionId, cwd: target?.cwd });
799
+ const { ACC_PARTICIPANT: _callerName, ...hostEnv } = env;
800
+ const context = await openContext({ event, adapterId, dataHome, runtime, env: hostEnv, deadline });
801
+ if (workspaceId !== undefined && context.descriptor.id !== workspaceId) return skipped("other_workspace");
802
+ return withSessionLifecycle({ root: context.paths.root, sessionId: event.sessionId,
803
+ clock: runtime.clock, deadlineAt: deadline }, async () => {
804
+ // As in runHook: recover a write a previous holder journalled before
805
+ // interpreting the binding.
806
+ const store = await openFilesystemStore({ root: context.paths.root, clock: runtime.clock,
807
+ ids: runtime.ids, workspaceId: context.descriptor.id, deadlineAt: deadline });
808
+ context.service = createCoordinationService({ store, clock: runtime.clock, ids: runtime.ids });
809
+ if (await loadSessionBinding({ runtimeDir: context.paths.root,
810
+ harnessSessionId: event.sessionId }) !== null) return skipped("known");
811
+ const started = await HANDLERS.sessionStart({ event, context, adapter, adapterId, binding: null,
812
+ paths: context.paths, readProcessTable: async () => new Map(), probeClientVersion,
813
+ platform, deadline, knownClientPid: clientPid, pins: false });
814
+ const session = await context.service.locateSession(started.accSessionId);
815
+ return { registered: true, sessionId: started.accSessionId,
816
+ participantId: session?.record.participantId ?? null, workspaceId: context.descriptor.id,
817
+ nativeBinding: started.nativeBinding };
818
+ });
819
+ };
820
+ let timer;
821
+ try {
822
+ return await Promise.race([register(), new Promise(resolve => {
823
+ timer = setTimeout(() => resolve(skipped("timed_out")), Math.max(0, deadline - Date.now()));
824
+ })]);
825
+ } catch {
826
+ return skipped("failed");
827
+ } finally {
828
+ clearTimeout(timer);
829
+ }
830
+ }
831
+
832
+ /**
833
+ * What a peer runs before it reads status (#167): ask every installed adapter
834
+ * that can see its client's sessions for the ones ACC could register, and
835
+ * register those in this workspace. Bounded by one budget and fail-open; the
836
+ * answer lists what was registered and is otherwise informational.
837
+ */
838
+ export async function registerDiscoveredSessions({ adapters, workspaceId, dataHome, env = {},
839
+ budgetMs = 2_500, runtime, probeClientVersion, platform }) {
840
+ const deadline = Date.now() + budgetMs;
841
+ const remaining = () => Math.max(0, deadline - Date.now());
842
+ // An adapter that ignores its timeout must not hold status: its answer is
843
+ // raced against what is left of the budget, and a late one is dropped.
844
+ const withinBudget = work => {
845
+ let timer;
846
+ return Promise.race([Promise.resolve().then(work).catch(() => []),
847
+ new Promise(resolve => { timer = setTimeout(resolve, remaining(), []); })])
848
+ .finally(() => clearTimeout(timer));
849
+ };
850
+ const registered = [];
851
+ let installed;
852
+ try {
853
+ installed = new Set((await loadOwnership({ dataHome })).installs.map(entry => entry.adapterId));
854
+ } catch {
855
+ return registered;
856
+ }
857
+ for (const adapter of Object.values(adapters ?? {})) {
858
+ if (typeof adapter?.discoverNativeSessions !== "function" || !installed.has(adapter.id)) continue;
859
+ const found = await withinBudget(() => adapter.discoverNativeSessions({ env,
860
+ timeoutMs: remaining() }));
861
+ for (const candidate of Array.isArray(found) ? found : []) {
862
+ if (remaining() === 0) return registered;
863
+ const result = await registerNativeSession({ adapterId: adapter.id, adapters,
864
+ event: { sessionId: candidate?.sessionId, cwd: candidate?.cwd }, clientPid: candidate?.clientPid,
865
+ workspaceId, dataHome, env, budgetMs: remaining(),
866
+ ...(runtime ? { runtime } : {}), ...(probeClientVersion ? { probeClientVersion } : {}),
867
+ ...(platform ? { platform } : {}) });
868
+ if (result.registered) {
869
+ registered.push({ adapterId: adapter.id, participantId: result.participantId,
870
+ sessionId: result.sessionId });
871
+ }
872
+ }
873
+ }
874
+ return registered;
875
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/installer",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -12,6 +12,8 @@ const REASONS = Object.freeze({
12
12
  probe_version_mismatch: "the local service and CLI versions differ",
13
13
  protocol_mismatch: "the local service did not confirm the required protocol",
14
14
  unsupported_shell: "automatic launch setup requires zsh",
15
+ client_session_embedded: "the client runs this session on its own embedded service, which peers "
16
+ + "cannot reach",
15
17
  });
16
18
 
17
19
  export function describeNativeReason(reason, { clientVersion, minimumVersion } = {}) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/mcp-server",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -118,6 +118,9 @@ async function callTool(name, args, context) {
118
118
 
119
119
  switch (name) {
120
120
  case "acc_status":
121
+ // Chats a client holds before their first turn, registered so this
122
+ // caller can address them (#167). Fail-open: status is read regardless.
123
+ await Promise.resolve().then(() => context.registerNativeSessions?.()).catch(() => null);
121
124
  return service.collectStatus({});
122
125
  case "acc_sync":
123
126
  return service.sync({ ...owner, cursor: args.cursor ?? null,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/protocol",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-can-communicate/storage-filesystem",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "exports": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-can-communicate",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "accManagedUpdateProtocol": 2,
5
5
  "accStoreVersion": 6,
6
6
  "type": "module",