@adapt-toolkit/a2adapt 0.6.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.
- package/dist/hooks/runner.js +83 -4
- package/dist/index.js +22 -3
- package/package.json +1 -1
package/dist/hooks/runner.js
CHANGED
|
@@ -4,10 +4,11 @@ import { createRequire } from 'node:module'; const require = createRequire(impor
|
|
|
4
4
|
// src/hooks/runner.ts
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
|
-
import { resolve, join } from "node:path";
|
|
7
|
+
import { resolve, join, dirname } from "node:path";
|
|
8
8
|
var STATE_DIR = resolve(
|
|
9
9
|
process.env.A2ADAPT_STATE_DIR ?? resolve(homedir(), ".a2adapt")
|
|
10
10
|
);
|
|
11
|
+
var IDENTITY_FILE = ".a2adapt-identity";
|
|
11
12
|
function readStdin() {
|
|
12
13
|
try {
|
|
13
14
|
return fs.readFileSync(0, "utf8");
|
|
@@ -72,23 +73,98 @@ ${lines.join("\n")}
|
|
|
72
73
|
|
|
73
74
|
To read: choose_identity({ name }) then get_messages() (returns the bodies and marks them read). To wait for live replies, arm a Monitor on the per-identity wake source \`a2adapt-mcp watch <name>\` (each new-mail line wakes you).`;
|
|
74
75
|
}
|
|
76
|
+
function findPinnedIdentity(start) {
|
|
77
|
+
let dir = resolve(start);
|
|
78
|
+
for (; ; ) {
|
|
79
|
+
let raw;
|
|
80
|
+
try {
|
|
81
|
+
raw = fs.readFileSync(join(dir, IDENTITY_FILE), "utf8");
|
|
82
|
+
} catch {
|
|
83
|
+
const parent = dirname(dir);
|
|
84
|
+
if (parent === dir) return null;
|
|
85
|
+
dir = parent;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const name = String(JSON.parse(raw).identity ?? "").trim();
|
|
90
|
+
return name || null;
|
|
91
|
+
} catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function identityExists(name) {
|
|
97
|
+
try {
|
|
98
|
+
return fs.statSync(join(STATE_DIR, name)).isDirectory();
|
|
99
|
+
} catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
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
|
+
}
|
|
120
|
+
function renderIdentityDirective(name, exists) {
|
|
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`;
|
|
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.`;
|
|
123
|
+
}
|
|
75
124
|
function sessionStart() {
|
|
76
125
|
const raw = readStdin();
|
|
77
126
|
let source = "";
|
|
127
|
+
let cwd = process.cwd();
|
|
78
128
|
if (raw) {
|
|
79
129
|
try {
|
|
80
|
-
|
|
130
|
+
const payload = JSON.parse(raw);
|
|
131
|
+
source = payload.source ?? "";
|
|
132
|
+
if (typeof payload.cwd === "string" && payload.cwd) cwd = payload.cwd;
|
|
81
133
|
} catch {
|
|
82
134
|
}
|
|
83
135
|
}
|
|
84
136
|
if (source === "compact") return noop();
|
|
137
|
+
const pinned = findPinnedIdentity(cwd);
|
|
85
138
|
const unread = collectUnread();
|
|
86
|
-
|
|
139
|
+
const blocks = [];
|
|
140
|
+
if (pinned) blocks.push(renderIdentityDirective(pinned, identityExists(pinned)));
|
|
141
|
+
if (unread.length > 0) blocks.push(renderContext(unread));
|
|
142
|
+
if (blocks.length === 0) return noop();
|
|
87
143
|
emit({
|
|
88
144
|
continue: true,
|
|
89
145
|
hookSpecificOutput: {
|
|
90
146
|
hookEventName: "SessionStart",
|
|
91
|
-
additionalContext:
|
|
147
|
+
additionalContext: blocks.join("\n\n")
|
|
148
|
+
}
|
|
149
|
+
});
|
|
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))
|
|
92
168
|
}
|
|
93
169
|
});
|
|
94
170
|
}
|
|
@@ -99,6 +175,9 @@ function main() {
|
|
|
99
175
|
case "session-start":
|
|
100
176
|
sessionStart();
|
|
101
177
|
return;
|
|
178
|
+
case "user-prompt-submit":
|
|
179
|
+
userPromptSubmit();
|
|
180
|
+
return;
|
|
102
181
|
default:
|
|
103
182
|
noop();
|
|
104
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.
|
|
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(
|
|
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.
|
|
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",
|