@adapt-toolkit/a2adapt 0.7.0 → 0.8.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.
@@ -100,9 +100,26 @@ function identityExists(name) {
100
100
  return false;
101
101
  }
102
102
  }
103
+ function anyIdentityBound() {
104
+ let snap;
105
+ try {
106
+ snap = JSON.parse(fs.readFileSync(join(STATE_DIR, "bindings.json"), "utf8"));
107
+ } catch {
108
+ return false;
109
+ }
110
+ if (!Array.isArray(snap.bound) || snap.bound.length === 0) return false;
111
+ const pid = Number(snap.pid);
112
+ if (!Number.isInteger(pid) || pid <= 0) return false;
113
+ try {
114
+ process.kill(pid, 0);
115
+ return true;
116
+ } catch (err) {
117
+ return err.code === "EPERM";
118
+ }
119
+ }
103
120
  function renderIdentityDirective(name, exists) {
104
121
  const bind = exists ? `call \`choose_identity({ name: "${name}" })\` to bind it to this session` : `it does not exist yet \u2014 call \`create_identity({ name: "${name}" })\` to create and bind it`;
105
- return `a2adapt \u2014 this workspace is pinned to identity "${name}" (via ${IDENTITY_FILE}). Before other a2adapt work, ${bind}. Then arm a Monitor on the wake source \`a2adapt-mcp watch ${name}\` so new mail wakes you. Do this once, up front; if a different identity is already bound, prefer this pinned one.`;
122
+ return `a2adapt \u2014 this workspace is pinned to identity "${name}" (via ${IDENTITY_FILE}). Before other a2adapt work, ${bind}. Then arm a Monitor on the wake source \`a2adapt-mcp watch ${name}\` so new mail wakes you. Do this once, up front. The pin is the workspace default: if the user explicitly asks to use a different identity, bind that instead \u2014 the user's choice always wins. If choose_identity reports the identity is held by another session, do NOT retry with force \u2014 tell the user it is bound elsewhere and ask whether to forcibly rebind it to this session; only pass force=true after they confirm.`;
106
123
  }
107
124
  function sessionStart() {
108
125
  const raw = readStdin();
@@ -131,6 +148,26 @@ function sessionStart() {
131
148
  }
132
149
  });
133
150
  }
151
+ function userPromptSubmit() {
152
+ const raw = readStdin();
153
+ let cwd = process.cwd();
154
+ if (raw) {
155
+ try {
156
+ const payload = JSON.parse(raw);
157
+ if (typeof payload.cwd === "string" && payload.cwd) cwd = payload.cwd;
158
+ } catch {
159
+ }
160
+ }
161
+ const pinned = findPinnedIdentity(cwd);
162
+ if (!pinned || anyIdentityBound()) return noop();
163
+ emit({
164
+ continue: true,
165
+ hookSpecificOutput: {
166
+ hookEventName: "UserPromptSubmit",
167
+ additionalContext: renderIdentityDirective(pinned, identityExists(pinned))
168
+ }
169
+ });
170
+ }
134
171
  function main() {
135
172
  const kind = process.argv[2] ?? "";
136
173
  try {
@@ -138,6 +175,9 @@ function main() {
138
175
  case "session-start":
139
176
  sessionStart();
140
177
  return;
178
+ case "user-prompt-submit":
179
+ userPromptSubmit();
180
+ return;
141
181
  default:
142
182
  noop();
143
183
  return;
package/dist/index.js CHANGED
@@ -22489,7 +22489,7 @@ function loadConfig() {
22489
22489
  }
22490
22490
 
22491
22491
  // src/index.ts
22492
- var VERSION = true ? "0.7.0" : "0.0.0-dev";
22492
+ var VERSION = true ? "0.8.0" : "0.0.0-dev";
22493
22493
  var CONFIG = loadConfig();
22494
22494
  var STATE_DIR = CONFIG.stateDir;
22495
22495
  var BROKER_URL = CONFIG.brokerUrl;
@@ -22531,6 +22531,17 @@ var identities = /* @__PURE__ */ new Map();
22531
22531
  var sessionBinding = /* @__PURE__ */ new Map();
22532
22532
  var bindingOwner = /* @__PURE__ */ new Map();
22533
22533
  var evictedSessions = /* @__PURE__ */ new Set();
22534
+ var bindingsSnapshotPath = () => join2(STATE_DIR, "bindings.json");
22535
+ function persistBindings() {
22536
+ try {
22537
+ fs2.mkdirSync(STATE_DIR, { recursive: true });
22538
+ const tmp = `${bindingsSnapshotPath()}.tmp`;
22539
+ fs2.writeFileSync(tmp, JSON.stringify({ pid: process.pid, bound: [...bindingOwner.keys()] }));
22540
+ fs2.renameSync(tmp, bindingsSnapshotPath());
22541
+ } catch (err) {
22542
+ log("failed to persist bindings snapshot:", String(err));
22543
+ }
22544
+ }
22534
22545
  var identityDir = (name) => join2(STATE_DIR, name);
22535
22546
  var seedPath = (dir) => join2(dir, "identity.seed");
22536
22547
  var dataPath = (dir) => join2(dir, "state_data.bin");
@@ -22771,6 +22782,7 @@ async function bootWrapper() {
22771
22782
  }
22772
22783
  }
22773
22784
  }
22785
+ persistBindings();
22774
22786
  }
22775
22787
  function resolveBound(sessionId) {
22776
22788
  if (evictedSessions.has(sessionId)) {
@@ -22784,6 +22796,7 @@ function resolveBound(sessionId) {
22784
22796
  if (!id) {
22785
22797
  sessionBinding.delete(sessionId);
22786
22798
  bindingOwner.delete(name);
22799
+ persistBindings();
22787
22800
  return { error: `The bound identity "${name}" no longer exists. Choose another with choose_identity.` };
22788
22801
  }
22789
22802
  return { id };
@@ -22796,6 +22809,7 @@ function bindSession(sessionId, name) {
22796
22809
  sessionBinding.set(sessionId, name);
22797
22810
  bindingOwner.set(name, sessionId);
22798
22811
  evictedSessions.delete(sessionId);
22812
+ persistBindings();
22799
22813
  }
22800
22814
  function renderContacts(v) {
22801
22815
  const out = [];
@@ -22879,7 +22893,7 @@ function createMcpServer(getSessionId) {
22879
22893
  );
22880
22894
  server.tool(
22881
22895
  "choose_identity",
22882
- "Bind an existing identity to this session so the messaging tools act as it. Binding is exclusive: if the identity is already in use by another session, this is declined unless force=true, which evicts the other session.",
22896
+ "Bind an existing identity to this session so the messaging tools act as it. Binding is exclusive: if the identity is already in use by another session, this is declined unless force=true, which evicts the other session. Never pass force=true on your own initiative \u2014 ask the user and get an explicit confirmation first.",
22883
22897
  {
22884
22898
  name: external_exports.string().min(1).describe("Name of the identity to bind."),
22885
22899
  force: external_exports.boolean().default(false).describe("Evict another session that holds this identity.")
@@ -22892,7 +22906,10 @@ function createMcpServer(getSessionId) {
22892
22906
  const holder = bindingOwner.get(name);
22893
22907
  if (holder && holder !== sid) {
22894
22908
  if (!force) {
22895
- return textResult(`choose_identity failed: "${name}" is currently bound to another session. Retry with force=true to take it over.`, true);
22909
+ return textResult(
22910
+ `choose_identity declined: "${name}" is currently bound to another session. Do not retry with force=true on your own \u2014 tell the user the identity is in use elsewhere and ask whether to forcibly rebind it here; only retry with force=true after they explicitly confirm.`,
22911
+ true
22912
+ );
22896
22913
  }
22897
22914
  evictedSessions.add(holder);
22898
22915
  sessionBinding.delete(holder);
@@ -22947,6 +22964,7 @@ ${lines.join("\n")}`);
22947
22964
  if (holder) {
22948
22965
  bindingOwner.delete(name);
22949
22966
  sessionBinding.delete(holder);
22967
+ persistBindings();
22950
22968
  }
22951
22969
  try {
22952
22970
  fs2.rmSync(id.dir, { recursive: true, force: true });
@@ -23234,6 +23252,7 @@ async function main() {
23234
23252
  if (name && bindingOwner.get(name) === sid) bindingOwner.delete(name);
23235
23253
  sessionBinding.delete(sid);
23236
23254
  evictedSessions.delete(sid);
23255
+ persistBindings();
23237
23256
  log(`session ${sid.slice(0, 8)}\u2026 closed`);
23238
23257
  }
23239
23258
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adapt-toolkit/a2adapt",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "MCP server daemon for a2adapt — one native ADAPT wrapper hosting N self-sovereign identities, exposing secure agent-to-agent messaging tools over HTTP (Streamable HTTP). Run `a2adapt-mcp start`.",
5
5
  "type": "module",
6
6
  "license": "MIT",