appduct 0.11.1 → 0.13.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/README.md +43 -38
- package/dist/bin.js +50 -36
- package/dist/cli/command-options.d.ts +7 -17
- package/dist/cli/result-types.d.ts +11 -8
- package/dist/cli/routes/daemon/index.js +12 -16
- package/dist/cli/routes/daemon/run.js +216 -46
- package/dist/cli/routes/daemon/start.js +18 -16
- package/dist/cli/routes/daemon/status.js +18 -16
- package/dist/cli/routes/daemon/stop.js +18 -16
- package/dist/cli/routes/doctor.js +12 -16
- package/dist/cli/routes/events/index.d.ts +9 -0
- package/dist/cli/routes/events/index.js +842 -0
- package/dist/cli/routes/events/shared-options.d.ts +12 -0
- package/dist/cli/routes/events/shared-options.js +102 -0
- package/dist/cli/routes/events/since.d.ts +6 -0
- package/dist/cli/routes/events/since.js +1977 -0
- package/dist/cli/routes/events/tail.d.ts +6 -0
- package/dist/cli/routes/{events.js → events/tail.js} +184 -162
- package/dist/cli/routes/init.js +14 -18
- package/dist/cli/routes/keygen.js +13 -16
- package/dist/cli/routes/mcp.js +193 -175
- package/dist/cli/routes/sessions/index.d.ts +7 -0
- package/dist/cli/routes/sessions/index.js +843 -0
- package/dist/cli/routes/sessions/link.d.ts +4 -0
- package/dist/cli/routes/{link.js → sessions/link.js} +19 -17
- package/dist/cli/routes/sessions/ls.d.ts +4 -0
- package/dist/cli/routes/{ls.js → sessions/ls.js} +19 -17
- package/dist/cli/routes/sessions/revoke.d.ts +4 -0
- package/dist/cli/routes/{revoke.js → sessions/revoke.js} +20 -18
- package/dist/cli/routes/tools/call.d.ts +4 -0
- package/dist/cli/routes/{invoke.js → tools/call.js} +21 -19
- package/dist/cli/routes/tools/describe.d.ts +6 -0
- package/dist/cli/routes/tools/describe.js +1837 -0
- package/dist/cli/routes/tools/index.d.ts +6 -0
- package/dist/cli/routes/tools/index.js +843 -0
- package/dist/cli/routes/tools/ls.d.ts +4 -0
- package/dist/cli/routes/{tools.js → tools/ls.js} +46 -73
- package/dist/cli/routes/tools/shared-options.d.ts +16 -0
- package/dist/cli/routes/tools/shared-options.js +122 -0
- package/dist/client/app-client.d.ts +96 -37
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.js +183 -139
- package/dist/commands/events.d.ts +31 -13
- package/dist/commands/invoke.d.ts +1 -1
- package/dist/commands/link.d.ts +1 -1
- package/dist/commands/ls.d.ts +1 -1
- package/dist/commands/revoke.d.ts +1 -1
- package/dist/commands/tools.d.ts +12 -13
- package/dist/daemon/config.d.ts +5 -2
- package/dist/daemon/daemon.d.ts +3 -0
- package/dist/daemon/event-bus.d.ts +36 -4
- package/dist/daemon/events-log.d.ts +31 -0
- package/dist/daemon/memory-append-only-file.d.ts +17 -0
- package/dist/daemon/node-append-only-file.d.ts +9 -0
- package/dist/daemon/rpc-errors.d.ts +1 -1
- package/dist/daemon/state-dir.d.ts +2 -0
- package/dist/errors.d.ts +1 -1
- package/dist/events/index.d.ts +60 -0
- package/dist/index.js +62 -39
- package/dist/link.d.ts +1 -1
- package/dist/mcp/app-tools.d.ts +3 -2
- package/dist/mcp/connect-tool.d.ts +1 -1
- package/dist/mcp/events-tool.d.ts +28 -34
- package/dist/native-scheme.d.ts +1 -1
- package/dist/output.d.ts +29 -6
- package/dist/rpc/client.d.ts +1 -1
- package/dist/scheme.d.ts +4 -4
- package/package.json +2 -2
- package/dist/cli/routes/events.d.ts +0 -3
- package/dist/cli/routes/invoke.d.ts +0 -3
- package/dist/cli/routes/link.d.ts +0 -3
- package/dist/cli/routes/ls.d.ts +0 -3
- package/dist/cli/routes/revoke.d.ts +0 -3
- package/dist/cli/routes/tools.d.ts +0 -3
package/dist/client/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/client/connect.ts
|
|
2
|
-
import { RPC_METHODS as
|
|
2
|
+
import { RPC_METHODS as RPC_METHODS4 } from "@appduct/shared";
|
|
3
3
|
|
|
4
4
|
// src/daemon/state-dir.ts
|
|
5
5
|
import { homedir } from "node:os";
|
|
@@ -14,6 +14,7 @@ var getStateDirPaths = (stateDir) => {
|
|
|
14
14
|
socketPath: join(stateDir, "daemon.sock"),
|
|
15
15
|
pidFilePath: join(stateDir, "daemon.pid"),
|
|
16
16
|
logFilePath: join(stateDir, "daemon.log"),
|
|
17
|
+
eventsLogPath: join(stateDir, "events.log"),
|
|
17
18
|
keyPath: join(stateDir, "key.pem"),
|
|
18
19
|
configPath: join(stateDir, "config.json"),
|
|
19
20
|
auditDir: join(stateDir, "audit"),
|
|
@@ -49,6 +50,7 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
49
50
|
"eventBufferSize",
|
|
50
51
|
"auditRetentionDays",
|
|
51
52
|
"daemonLogMaxBytes",
|
|
53
|
+
"eventsLogMaxBytes",
|
|
52
54
|
"policy",
|
|
53
55
|
"advertisedIp",
|
|
54
56
|
"scheme",
|
|
@@ -108,6 +110,7 @@ var defaultConfig = (paths) => {
|
|
|
108
110
|
eventBufferSize: 256,
|
|
109
111
|
auditRetentionDays: DEFAULT_AUDIT_RETENTION_DAYS,
|
|
110
112
|
daemonLogMaxBytes: DEFAULT_DAEMON_LOG_MAX_BYTES,
|
|
113
|
+
eventsLogMaxBytes: 10 * 1024 * 1024,
|
|
111
114
|
restartDaemonOnVersionMismatch: false,
|
|
112
115
|
policy: {
|
|
113
116
|
default: "allow",
|
|
@@ -170,6 +173,9 @@ var loadConfig = async (paths, options = {}) => {
|
|
|
170
173
|
if (parsed.daemonLogMaxBytes !== void 0) {
|
|
171
174
|
config.daemonLogMaxBytes = requirePositiveInteger(parsed.daemonLogMaxBytes, "daemonLogMaxBytes");
|
|
172
175
|
}
|
|
176
|
+
if (parsed.eventsLogMaxBytes !== void 0) {
|
|
177
|
+
config.eventsLogMaxBytes = requirePositiveInteger(parsed.eventsLogMaxBytes, "eventsLogMaxBytes");
|
|
178
|
+
}
|
|
173
179
|
if (parsed.restartDaemonOnVersionMismatch !== void 0) {
|
|
174
180
|
config.restartDaemonOnVersionMismatch = requireBoolean(
|
|
175
181
|
parsed.restartDaemonOnVersionMismatch,
|
|
@@ -1023,8 +1029,108 @@ var openDaemonStream = async (options) => {
|
|
|
1023
1029
|
import {
|
|
1024
1030
|
clampToolTimeoutMs,
|
|
1025
1031
|
MAX_TOOL_TIMEOUT_MS,
|
|
1026
|
-
RPC_METHODS as
|
|
1032
|
+
RPC_METHODS as RPC_METHODS3,
|
|
1033
|
+
toAppEvent as toAppEvent2
|
|
1034
|
+
} from "@appduct/shared";
|
|
1035
|
+
|
|
1036
|
+
// src/events/index.ts
|
|
1037
|
+
import {
|
|
1038
|
+
RPC_METHODS as RPC_METHODS2,
|
|
1039
|
+
toAppEvent
|
|
1027
1040
|
} from "@appduct/shared";
|
|
1041
|
+
var WaitForAppEventTimeoutError = class extends Error {
|
|
1042
|
+
constructor(eventName, timeoutMs) {
|
|
1043
|
+
super(`Timed out after ${timeoutMs}ms waiting for event "${eventName}".`);
|
|
1044
|
+
this.eventName = eventName;
|
|
1045
|
+
this.timeoutMs = timeoutMs;
|
|
1046
|
+
this.name = "WaitForAppEventTimeoutError";
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
var WaitForAppEventConnectionClosedError = class extends Error {
|
|
1050
|
+
constructor(eventName) {
|
|
1051
|
+
super(`The connection to the Appduct daemon closed while waiting for event "${eventName}".`);
|
|
1052
|
+
this.eventName = eventName;
|
|
1053
|
+
this.name = "WaitForAppEventConnectionClosedError";
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
var waitForAppEvent = async (stream, options) => {
|
|
1057
|
+
const { sessionId, name, since, timeoutMs, payloadMaxBytes } = options;
|
|
1058
|
+
let liveHandler = null;
|
|
1059
|
+
const earlyEvents = [];
|
|
1060
|
+
const unsubscribeNotification = stream.onNotification((payload) => {
|
|
1061
|
+
const event = payload;
|
|
1062
|
+
if (liveHandler) {
|
|
1063
|
+
liveHandler(event);
|
|
1064
|
+
} else {
|
|
1065
|
+
earlyEvents.push(event);
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1068
|
+
let unsubscribeClose = () => {
|
|
1069
|
+
};
|
|
1070
|
+
try {
|
|
1071
|
+
await stream.call(RPC_METHODS2.eventsSubscribe, {
|
|
1072
|
+
sessionSelector: sessionId,
|
|
1073
|
+
kinds: ["app_event"],
|
|
1074
|
+
name,
|
|
1075
|
+
payloadMaxBytes
|
|
1076
|
+
});
|
|
1077
|
+
const sinceResult = await stream.call(RPC_METHODS2.eventsSince, {
|
|
1078
|
+
selector: sessionId,
|
|
1079
|
+
since,
|
|
1080
|
+
name,
|
|
1081
|
+
payloadMaxBytes
|
|
1082
|
+
});
|
|
1083
|
+
const seenSeqs = /* @__PURE__ */ new Set();
|
|
1084
|
+
const backlog = [];
|
|
1085
|
+
for (const event of [...sinceResult.events, ...earlyEvents]) {
|
|
1086
|
+
if (event.sessionId === sessionId && !seenSeqs.has(event.seq)) {
|
|
1087
|
+
seenSeqs.add(event.seq);
|
|
1088
|
+
backlog.push(event);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
backlog.sort((a, b) => a.seq - b.seq);
|
|
1092
|
+
for (const event of backlog) {
|
|
1093
|
+
const appEvent = toAppEvent(event, sessionId);
|
|
1094
|
+
if (appEvent) {
|
|
1095
|
+
return { event: appEvent, dropped: sinceResult.dropped };
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
const highestConsideredSeq = backlog.length > 0 ? backlog[backlog.length - 1].seq : sinceResult.cursor;
|
|
1099
|
+
return await new Promise((resolve2, reject) => {
|
|
1100
|
+
let settled = false;
|
|
1101
|
+
const settle = (fn) => {
|
|
1102
|
+
if (settled) {
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
settled = true;
|
|
1106
|
+
clearTimeout(timer);
|
|
1107
|
+
liveHandler = null;
|
|
1108
|
+
fn();
|
|
1109
|
+
};
|
|
1110
|
+
const timer = setTimeout(() => {
|
|
1111
|
+
settle(() => reject(new WaitForAppEventTimeoutError(name, timeoutMs)));
|
|
1112
|
+
}, timeoutMs);
|
|
1113
|
+
unsubscribeClose = stream.onClose(() => {
|
|
1114
|
+
settle(() => reject(new WaitForAppEventConnectionClosedError(name)));
|
|
1115
|
+
});
|
|
1116
|
+
liveHandler = (event) => {
|
|
1117
|
+
if (settled) {
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
if (event.sessionId === sessionId && event.seq <= highestConsideredSeq) {
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
const appEvent = toAppEvent(event, sessionId);
|
|
1124
|
+
if (appEvent) {
|
|
1125
|
+
settle(() => resolve2({ event: appEvent, dropped: sinceResult.dropped }));
|
|
1126
|
+
}
|
|
1127
|
+
};
|
|
1128
|
+
});
|
|
1129
|
+
} finally {
|
|
1130
|
+
unsubscribeNotification();
|
|
1131
|
+
unsubscribeClose();
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
1028
1134
|
|
|
1029
1135
|
// src/errors.ts
|
|
1030
1136
|
var AppductCliError = class extends Error {
|
|
@@ -1097,12 +1203,14 @@ var transportTimeoutForToolCall = (timeoutMs) => {
|
|
|
1097
1203
|
const clamped = timeoutMs === void 0 || !Number.isFinite(timeoutMs) ? MAX_TOOL_TIMEOUT_MS : clampToolTimeoutMs(timeoutMs);
|
|
1098
1204
|
return clamped + CALL_TRANSPORT_TIMEOUT_SLACK_MS;
|
|
1099
1205
|
};
|
|
1100
|
-
var makeAppClient = (stream, sessionId) => {
|
|
1206
|
+
var makeAppClient = (stream, sessionId, openStream) => {
|
|
1207
|
+
let closed = false;
|
|
1208
|
+
const openWaitStreams = /* @__PURE__ */ new Set();
|
|
1101
1209
|
const client = {
|
|
1102
1210
|
sessionId,
|
|
1103
1211
|
tools: async () => {
|
|
1104
1212
|
try {
|
|
1105
|
-
const result = await stream.call(
|
|
1213
|
+
const result = await stream.call(RPC_METHODS3.toolsList, {
|
|
1106
1214
|
selector: sessionId
|
|
1107
1215
|
});
|
|
1108
1216
|
const entries = Array.isArray(result) ? result : result.tools;
|
|
@@ -1114,7 +1222,7 @@ var makeAppClient = (stream, sessionId) => {
|
|
|
1114
1222
|
call: async (name, args, options) => {
|
|
1115
1223
|
try {
|
|
1116
1224
|
const result = await stream.call(
|
|
1117
|
-
|
|
1225
|
+
RPC_METHODS3.toolsCall,
|
|
1118
1226
|
{
|
|
1119
1227
|
selector: sessionId,
|
|
1120
1228
|
name,
|
|
@@ -1129,153 +1237,82 @@ var makeAppClient = (stream, sessionId) => {
|
|
|
1129
1237
|
throw toAppductError(error);
|
|
1130
1238
|
}
|
|
1131
1239
|
},
|
|
1240
|
+
// A single implementation backs both of the public type's overloaded signatures — `makeAppClient`
|
|
1241
|
+
// returns `client as unknown as AppClient<TTools>` below, so nothing here has to structurally
|
|
1242
|
+
// satisfy either signature on its own; the overloads are strictly a compile-time view for callers.
|
|
1132
1243
|
events: async (options = {}) => {
|
|
1133
1244
|
try {
|
|
1134
|
-
const result = await stream.call(
|
|
1245
|
+
const result = await stream.call(RPC_METHODS3.eventsSince, {
|
|
1135
1246
|
selector: sessionId,
|
|
1136
1247
|
since: options.since,
|
|
1137
1248
|
limit: options.limit,
|
|
1138
|
-
|
|
1249
|
+
payloadMaxBytes: options.payloadMaxBytes
|
|
1139
1250
|
});
|
|
1140
1251
|
return {
|
|
1141
|
-
events: result.events.map((event) =>
|
|
1142
|
-
cursor: result.cursor
|
|
1252
|
+
events: result.events.map((event) => toAppEvent2(event, sessionId)).filter((event) => event !== void 0),
|
|
1253
|
+
cursor: result.cursor,
|
|
1254
|
+
dropped: result.dropped,
|
|
1255
|
+
remaining: result.remaining
|
|
1143
1256
|
};
|
|
1144
1257
|
} catch (error) {
|
|
1145
1258
|
throw toAppductError(error);
|
|
1146
1259
|
}
|
|
1147
1260
|
},
|
|
1148
|
-
waitForEvent: (name, options = {}) => {
|
|
1261
|
+
waitForEvent: async (name, options = {}) => {
|
|
1262
|
+
if (closed) {
|
|
1263
|
+
throw new AppductError(
|
|
1264
|
+
"connection_error",
|
|
1265
|
+
`Cannot wait for event "${name}": this AppClient (session "${sessionId}") is closed.`
|
|
1266
|
+
);
|
|
1267
|
+
}
|
|
1149
1268
|
const timeoutMs = options.timeoutMs ?? DEFAULT_WAIT_FOR_EVENT_TIMEOUT_MS;
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1269
|
+
let waitStream;
|
|
1270
|
+
try {
|
|
1271
|
+
waitStream = await openStream();
|
|
1272
|
+
} catch (error) {
|
|
1273
|
+
throw toAppductError(error);
|
|
1274
|
+
}
|
|
1275
|
+
if (closed) {
|
|
1276
|
+
waitStream.close();
|
|
1277
|
+
throw new AppductError(
|
|
1278
|
+
"connection_error",
|
|
1279
|
+
`Cannot wait for event "${name}": this AppClient (session "${sessionId}") is closed.`
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
openWaitStreams.add(waitStream);
|
|
1283
|
+
try {
|
|
1284
|
+
const { event } = await waitForAppEvent(waitStream, {
|
|
1285
|
+
sessionId,
|
|
1286
|
+
name,
|
|
1287
|
+
since: options.since,
|
|
1288
|
+
timeoutMs,
|
|
1289
|
+
payloadMaxBytes: options.payloadMaxBytes
|
|
1290
|
+
});
|
|
1291
|
+
return event;
|
|
1292
|
+
} catch (error) {
|
|
1293
|
+
if (error instanceof WaitForAppEventTimeoutError) {
|
|
1294
|
+
throw new AppductError("timeout", error.message);
|
|
1154
1295
|
}
|
|
1155
|
-
if (
|
|
1156
|
-
|
|
1296
|
+
if (error instanceof WaitForAppEventConnectionClosedError) {
|
|
1297
|
+
throw new AppductError("connection_error", error.message);
|
|
1157
1298
|
}
|
|
1158
|
-
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
if (settled) {
|
|
1164
|
-
return;
|
|
1165
|
-
}
|
|
1166
|
-
settled = true;
|
|
1167
|
-
clearTimeout(timer);
|
|
1168
|
-
unsubscribeClose();
|
|
1169
|
-
liveHandler = null;
|
|
1170
|
-
fn();
|
|
1171
|
-
};
|
|
1172
|
-
const timer = setTimeout(() => {
|
|
1173
|
-
settle(
|
|
1174
|
-
() => reject(new AppductError("timeout", `Timed out after ${timeoutMs}ms waiting for event "${name}".`))
|
|
1175
|
-
);
|
|
1176
|
-
}, timeoutMs);
|
|
1177
|
-
let liveHandler = null;
|
|
1178
|
-
const earlyEvents = [];
|
|
1179
|
-
const unsubscribeNotification = stream.onNotification((payload) => {
|
|
1180
|
-
const event = payload;
|
|
1181
|
-
if (liveHandler) {
|
|
1182
|
-
liveHandler(event);
|
|
1183
|
-
} else {
|
|
1184
|
-
earlyEvents.push(event);
|
|
1185
|
-
}
|
|
1186
|
-
});
|
|
1187
|
-
let unsubscribeClose = () => {
|
|
1188
|
-
};
|
|
1189
|
-
const fail = (error) => {
|
|
1190
|
-
settle(() => reject(toAppductError(error)));
|
|
1191
|
-
unsubscribeNotification();
|
|
1192
|
-
};
|
|
1193
|
-
(async () => {
|
|
1194
|
-
await stream.call(RPC_METHODS2.eventsSubscribe, { sessionSelector: sessionId, kinds: ["app_event"] });
|
|
1195
|
-
const sinceResult = await stream.call(RPC_METHODS2.eventsSince, {
|
|
1196
|
-
selector: sessionId,
|
|
1197
|
-
since: options.since,
|
|
1198
|
-
kinds: ["app_event"]
|
|
1199
|
-
});
|
|
1200
|
-
const seenSeqs = /* @__PURE__ */ new Set();
|
|
1201
|
-
const backlog = [];
|
|
1202
|
-
for (const event of [...sinceResult.events, ...earlyEvents]) {
|
|
1203
|
-
if (event.sessionId === sessionId && !seenSeqs.has(event.seq)) {
|
|
1204
|
-
seenSeqs.add(event.seq);
|
|
1205
|
-
backlog.push(event);
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
backlog.sort((a, b) => a.seq - b.seq);
|
|
1209
|
-
for (const event of backlog) {
|
|
1210
|
-
try {
|
|
1211
|
-
const matched = toMatch(event);
|
|
1212
|
-
if (matched) {
|
|
1213
|
-
settle(() => resolve2(matched));
|
|
1214
|
-
unsubscribeNotification();
|
|
1215
|
-
return;
|
|
1216
|
-
}
|
|
1217
|
-
} catch (error) {
|
|
1218
|
-
fail(error);
|
|
1219
|
-
return;
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
const highestConsideredSeq = backlog.length > 0 ? backlog[backlog.length - 1].seq : sinceResult.cursor;
|
|
1223
|
-
unsubscribeClose = stream.onClose(() => {
|
|
1224
|
-
settle(
|
|
1225
|
-
() => reject(
|
|
1226
|
-
new AppductError(
|
|
1227
|
-
"connection_error",
|
|
1228
|
-
`The connection to the Appduct daemon closed while waiting for event "${name}".`
|
|
1229
|
-
)
|
|
1230
|
-
)
|
|
1231
|
-
);
|
|
1232
|
-
unsubscribeNotification();
|
|
1233
|
-
});
|
|
1234
|
-
liveHandler = (event) => {
|
|
1235
|
-
if (settled) {
|
|
1236
|
-
return;
|
|
1237
|
-
}
|
|
1238
|
-
if (event.sessionId === sessionId && event.seq <= highestConsideredSeq) {
|
|
1239
|
-
return;
|
|
1240
|
-
}
|
|
1241
|
-
try {
|
|
1242
|
-
const matched = toMatch(event);
|
|
1243
|
-
if (matched) {
|
|
1244
|
-
settle(() => resolve2(matched));
|
|
1245
|
-
unsubscribeNotification();
|
|
1246
|
-
}
|
|
1247
|
-
} catch (error) {
|
|
1248
|
-
fail(error);
|
|
1249
|
-
}
|
|
1250
|
-
};
|
|
1251
|
-
})().catch((error) => {
|
|
1252
|
-
fail(error);
|
|
1253
|
-
});
|
|
1254
|
-
});
|
|
1299
|
+
throw toAppductError(error);
|
|
1300
|
+
} finally {
|
|
1301
|
+
openWaitStreams.delete(waitStream);
|
|
1302
|
+
waitStream.close();
|
|
1303
|
+
}
|
|
1255
1304
|
},
|
|
1256
1305
|
close: () => {
|
|
1306
|
+
closed = true;
|
|
1307
|
+
for (const waitStream of openWaitStreams) {
|
|
1308
|
+
waitStream.close();
|
|
1309
|
+
}
|
|
1310
|
+
openWaitStreams.clear();
|
|
1257
1311
|
stream.close();
|
|
1258
1312
|
}
|
|
1259
1313
|
};
|
|
1260
1314
|
return client;
|
|
1261
1315
|
};
|
|
1262
|
-
var appEventFrom = (event, sessionId) => {
|
|
1263
|
-
if (event.kind !== "app_event" || event.sessionId !== sessionId) {
|
|
1264
|
-
return void 0;
|
|
1265
|
-
}
|
|
1266
|
-
const data = event.data;
|
|
1267
|
-
if (typeof data.name !== "string") {
|
|
1268
|
-
return void 0;
|
|
1269
|
-
}
|
|
1270
|
-
return {
|
|
1271
|
-
name: data.name,
|
|
1272
|
-
payload: data.payload,
|
|
1273
|
-
ts: event.ts,
|
|
1274
|
-
sessionId: event.sessionId,
|
|
1275
|
-
alias: event.alias,
|
|
1276
|
-
seq: event.seq
|
|
1277
|
-
};
|
|
1278
|
-
};
|
|
1279
1316
|
|
|
1280
1317
|
// src/client/connect.ts
|
|
1281
1318
|
var connect3 = async (options = {}) => {
|
|
@@ -1292,10 +1329,16 @@ var connect3 = async (options = {}) => {
|
|
|
1292
1329
|
throw toAppductError(error);
|
|
1293
1330
|
}
|
|
1294
1331
|
try {
|
|
1295
|
-
const detail = await stream.call(
|
|
1332
|
+
const detail = await stream.call(RPC_METHODS4.sessionsDescribe, {
|
|
1296
1333
|
selector: options.selector
|
|
1297
1334
|
});
|
|
1298
|
-
|
|
1335
|
+
const openStream = () => openDaemonStream({
|
|
1336
|
+
stateDir,
|
|
1337
|
+
spawn: options.spawn,
|
|
1338
|
+
autoSpawn: options.autoSpawn,
|
|
1339
|
+
requestTimeoutMs: options.requestTimeoutMs
|
|
1340
|
+
});
|
|
1341
|
+
return makeAppClient(stream, detail.sessionId, openStream);
|
|
1299
1342
|
} catch (error) {
|
|
1300
1343
|
stream.close();
|
|
1301
1344
|
throw toAppductError(error);
|
|
@@ -1303,7 +1346,7 @@ var connect3 = async (options = {}) => {
|
|
|
1303
1346
|
};
|
|
1304
1347
|
|
|
1305
1348
|
// src/client/bootstrap.ts
|
|
1306
|
-
import { RPC_METHODS as
|
|
1349
|
+
import { RPC_METHODS as RPC_METHODS6 } from "@appduct/shared";
|
|
1307
1350
|
|
|
1308
1351
|
// src/cli/open-target.ts
|
|
1309
1352
|
import { execFile } from "node:child_process";
|
|
@@ -1600,7 +1643,7 @@ var deliverToOpenTarget = async (options) => {
|
|
|
1600
1643
|
};
|
|
1601
1644
|
|
|
1602
1645
|
// src/link.ts
|
|
1603
|
-
import { RPC_METHODS as
|
|
1646
|
+
import { RPC_METHODS as RPC_METHODS5 } from "@appduct/shared";
|
|
1604
1647
|
|
|
1605
1648
|
// src/scheme.ts
|
|
1606
1649
|
import { existsSync as existsSync2 } from "node:fs";
|
|
@@ -2248,7 +2291,7 @@ var mintLink = async (options) => {
|
|
|
2248
2291
|
throw usageError(invalidAppIdMessage(appId));
|
|
2249
2292
|
}
|
|
2250
2293
|
const result = await callDaemon(
|
|
2251
|
-
|
|
2294
|
+
RPC_METHODS5.linkCreate,
|
|
2252
2295
|
{
|
|
2253
2296
|
ttlSeconds: options.ttlSeconds,
|
|
2254
2297
|
addressOverride: options.target && usesLoopbackAddress(options.target) ? OPEN_TARGET_ADDRESS_OVERRIDE : void 0
|
|
@@ -2321,10 +2364,11 @@ var waitForSession = async (sessionId, options = {}) => {
|
|
|
2321
2364
|
} catch (error) {
|
|
2322
2365
|
throw toAppductError(error);
|
|
2323
2366
|
}
|
|
2367
|
+
const openStream = () => openDaemonStream({ stateDir, spawn: options.spawn, autoSpawn: options.autoSpawn });
|
|
2324
2368
|
try {
|
|
2325
2369
|
try {
|
|
2326
|
-
await stream.call(
|
|
2327
|
-
return makeAppClient(stream, sessionId);
|
|
2370
|
+
await stream.call(RPC_METHODS6.sessionsDescribe, { selector: sessionId });
|
|
2371
|
+
return makeAppClient(stream, sessionId, openStream);
|
|
2328
2372
|
} catch (error) {
|
|
2329
2373
|
if (!(error instanceof DaemonRpcError) || error.data?.type !== "unknown_session") {
|
|
2330
2374
|
throw error;
|
|
@@ -2371,11 +2415,11 @@ var waitForSession = async (sessionId, options = {}) => {
|
|
|
2371
2415
|
settle(resolve2);
|
|
2372
2416
|
}
|
|
2373
2417
|
});
|
|
2374
|
-
stream.call(
|
|
2418
|
+
stream.call(RPC_METHODS6.eventsSubscribe, { sessionSelector: sessionId, kinds: ["session_claimed"] }).catch((error) => {
|
|
2375
2419
|
settle(() => reject(toAppductError(error)));
|
|
2376
2420
|
});
|
|
2377
2421
|
});
|
|
2378
|
-
return makeAppClient(stream, sessionId);
|
|
2422
|
+
return makeAppClient(stream, sessionId, openStream);
|
|
2379
2423
|
} catch (error) {
|
|
2380
2424
|
stream.close();
|
|
2381
2425
|
throw toAppductError(error);
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `appduct events` (ARCHITECTURE.md §10): subscribes to `events.subscribe`
|
|
3
|
-
* connection and streams
|
|
4
|
-
* the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* `appduct events tail` (ARCHITECTURE.md §10): subscribes to `events.subscribe` for `app_event`
|
|
3
|
+
* only over a persistent connection and streams the app's own events until interrupted (Ctrl-C) or
|
|
4
|
+
* the connection drops. Appduct's lifecycle and tool-call kinds are not printed (issue #98). This
|
|
5
|
+
* is the one command whose output isn't a single rendered `CliResult` — each event is written as
|
|
6
|
+
* it arrives (NDJSON under `--json`, a human line otherwise) — so it plugs into
|
|
7
|
+
* `executeHostedCommand` with a live reporter that suppresses the default one-shot bootstrap
|
|
8
|
+
* render (`cli/runner.ts`).
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
* of a live subscription
|
|
10
|
-
* is written through `onEvent` exactly like a live
|
|
11
|
-
* "hosted command" here just completes immediately
|
|
10
|
+
* `appduct events since <cursor>` (issue #6) is a one-shot mode: a single `events.since` pull
|
|
11
|
+
* instead of a live subscription, selected here by `since !== undefined`. It falls out of the same
|
|
12
|
+
* `EventsHostedResult` shape (each retained event is written through `onEvent` exactly like a live
|
|
13
|
+
* one) so the CLI plumbing needs no forking — the "hosted command" here just completes immediately
|
|
14
|
+
* instead of running until interrupted.
|
|
12
15
|
*/
|
|
13
16
|
import { type EventNotification } from "@appduct/shared";
|
|
14
17
|
import type { CliResult } from "../cli/result-types.js";
|
|
@@ -18,15 +21,30 @@ export type EventsCommandOptions = {
|
|
|
18
21
|
/** Exclusive lower bound on `EventNotification.seq`; switches this command to a one-shot
|
|
19
22
|
* `events.since` pull instead of a live `events.subscribe` stream. */
|
|
20
23
|
since?: number;
|
|
24
|
+
/** Whole-name, case-sensitive glob (issue #115), forwarded to `events.subscribe`/`events.since`.
|
|
25
|
+
* No default — everything is delivered unfiltered unless asked. */
|
|
26
|
+
name?: string;
|
|
27
|
+
/** Caps an `app_event`'s payload to this many UTF-8 bytes of its JSON (issue #115), forwarded to
|
|
28
|
+
* `events.subscribe`/`events.since`. No default — nothing is truncated unless asked. */
|
|
29
|
+
payloadMaxBytes?: number;
|
|
30
|
+
};
|
|
31
|
+
/** `events since` mode's resulting cursor, plus how far behind or ahead of the full picture it
|
|
32
|
+
* is (issue #115): `dropped` events fell off the retention buffer before this pull, `remaining`
|
|
33
|
+
* matching events are still waiting beyond this page. */
|
|
34
|
+
export type EventsSinceCursor = {
|
|
35
|
+
cursor: number;
|
|
36
|
+
dropped: number;
|
|
37
|
+
remaining: number;
|
|
21
38
|
};
|
|
22
39
|
export type EventsCommandContext = {
|
|
23
40
|
stateDir: string;
|
|
24
41
|
spawn?: SpawnFn;
|
|
25
42
|
onEvent: (event: EventNotification) => void;
|
|
26
|
-
/**
|
|
27
|
-
* doesn't have to reconstruct it by maxing `seq` over the
|
|
28
|
-
* response is empty — the whole point of a cursor is
|
|
29
|
-
|
|
43
|
+
/** `events since` mode only: called once with the pull's resulting cursor (plus `dropped`/
|
|
44
|
+
* `remaining`), so a scripted caller doesn't have to reconstruct it by maxing `seq` over the
|
|
45
|
+
* printed lines (impossible when the response is empty — the whole point of a cursor is
|
|
46
|
+
* knowing where to resume from either way). */
|
|
47
|
+
onCursor?: (cursor: EventsSinceCursor) => void;
|
|
30
48
|
};
|
|
31
49
|
export type EventsHostedResult = {
|
|
32
50
|
result: CliResult<never>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `appduct
|
|
2
|
+
* `appduct tools call` (ARCHITECTURE.md §10): `tools.call` round trip. Failures propagate as-is
|
|
3
3
|
* (`DaemonRpcError`, preserving `error.data.type` verbatim) — `errors.ts`'s `toCliError` is what
|
|
4
4
|
* renders `error.type` as the app's wire error type end-to-end, not this module's job.
|
|
5
5
|
*/
|
package/dist/commands/link.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `appduct link` (ARCHITECTURE.md §10, §8): CLI-flag validation (`--open`/`--device`'s
|
|
2
|
+
* `appduct sessions link` (ARCHITECTURE.md §10, §8): CLI-flag validation (`--open`/`--device`'s
|
|
3
3
|
* CLI-specific error wording) around the shared `mintLink` core (`../link.ts`), which both this
|
|
4
4
|
* command and `appduct/client`'s `link()` use so the deep-link shape can't drift between them.
|
|
5
5
|
*/
|
package/dist/commands/ls.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** `appduct ls` (ARCHITECTURE.md §10): `sessions.list` passthrough. */
|
|
1
|
+
/** `appduct sessions ls` (ARCHITECTURE.md §10): `sessions.list` passthrough. */
|
|
2
2
|
import type { CliResult, LsCommandData } from "../cli/result-types.js";
|
|
3
3
|
import { type SpawnFn } from "../rpc/client.js";
|
|
4
4
|
export type LsCommandContext = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** `appduct revoke` (ARCHITECTURE.md §10): revokes a session, optionally by selector. */
|
|
1
|
+
/** `appduct sessions revoke` (ARCHITECTURE.md §10): revokes a session, optionally by selector. */
|
|
2
2
|
import type { CliResult, RevokeCommandData } from "../cli/result-types.js";
|
|
3
3
|
import { type SpawnFn } from "../rpc/client.js";
|
|
4
4
|
export type RevokeCommandOptions = {
|
package/dist/commands/tools.d.ts
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `appduct tools` (ARCHITECTURE.md §10): `
|
|
3
|
-
* <text>] [--limit <n>] [--offset <n>]` lists tools for a
|
|
4
|
-
* lists only the session's groups with their tool counts;
|
|
5
|
-
* tool's full schema/annotations.
|
|
2
|
+
* `appduct tools ls`/`appduct tools describe` (ARCHITECTURE.md §10, issue #96): `ls [selector]
|
|
3
|
+
* [--full] [--group <name>] [--filter <text>] [--limit <n>] [--offset <n>]` lists tools for a
|
|
4
|
+
* session; `ls [selector] --groups` lists only the session's groups with their tool counts;
|
|
5
|
+
* `describe [selector] <name>` shows one tool's full schema/annotations.
|
|
6
6
|
*
|
|
7
|
-
* The
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* treating it as a selector and list that session's tools instead.
|
|
7
|
+
* The two verbs (`cli/routes/tools/ls.ts` and `cli/routes/tools/describe.ts`) split `[selector]`
|
|
8
|
+
* from `<name>` unambiguously before this handler ever runs — `describe`'s `<name>` is always the
|
|
9
|
+
* last positional, required, never a candidate selector — so there is no probing here: a listing
|
|
10
|
+
* request (`options.name === undefined`) always lists, a lookup (`options.name !== undefined`)
|
|
11
|
+
* always looks up, on the given selector or the implicit session if none was given.
|
|
13
12
|
*
|
|
14
|
-
* `--group`/`--filter`/`--limit`/`--offset` only ever reach the daemon on a *listing* request: the
|
|
15
|
-
* path
|
|
16
|
-
*
|
|
13
|
+
* `--group`/`--filter`/`--limit`/`--offset` only ever reach the daemon on a *listing* request: the
|
|
14
|
+
* detail path always asks for the whole, unpaged registry, so a name lookup can never miss a tool
|
|
15
|
+
* that paging would have left off a page.
|
|
17
16
|
*/
|
|
18
17
|
import type { CliResult, ToolsCommandData } from "../cli/result-types.js";
|
|
19
18
|
import { type SpawnFn } from "../rpc/client.js";
|
package/dist/daemon/config.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export type AppductConfig = {
|
|
|
34
34
|
graceSeconds: number;
|
|
35
35
|
linkTtlSeconds: number;
|
|
36
36
|
keepaliveIntervalSeconds: number;
|
|
37
|
-
/** Max retained `app_event
|
|
37
|
+
/** Max retained `app_event`s per session (ARCHITECTURE.md §5's `events.since`
|
|
38
38
|
* retention buffer); default 256. */
|
|
39
39
|
eventBufferSize: number;
|
|
40
40
|
/** Days of `audit/<YYYY-MM-DD>.jsonl` history to keep; files older than this are pruned on
|
|
@@ -43,6 +43,9 @@ export type AppductConfig = {
|
|
|
43
43
|
/** Size at which `daemon.log` is rotated to `daemon.log.1` before a daemon is spawned
|
|
44
44
|
* (ARCHITECTURE.md §3/§4). Default 10 MiB. */
|
|
45
45
|
daemonLogMaxBytes: number;
|
|
46
|
+
/** Size past which the daemon rotates `events.log` to `events.log.1` (ARCHITECTURE.md §3).
|
|
47
|
+
* Default 10 MiB. */
|
|
48
|
+
eventsLogMaxBytes: number;
|
|
46
49
|
policy: AppductPolicyConfig;
|
|
47
50
|
/**
|
|
48
51
|
* When the CLI/MCP client finds the running daemon on a different Appduct version, restart it
|
|
@@ -54,7 +57,7 @@ export type AppductConfig = {
|
|
|
54
57
|
/** Operator override for advertised-address detection (daemon/address.ts); undefined = auto-detect. */
|
|
55
58
|
advertisedIp?: string;
|
|
56
59
|
/**
|
|
57
|
-
* Deep-link URI scheme used to compose `appduct link`'s output (ARCHITECTURE.md §10: "taken
|
|
60
|
+
* Deep-link URI scheme used to compose `appduct sessions link`'s output (ARCHITECTURE.md §10: "taken
|
|
58
61
|
* from the flag, else `config.json`, else the CLI errors with a clear message"). Not part of the
|
|
59
62
|
* `config.json` shape enumerated in ARCHITECTURE.md §3 (which only covers daemon-side settings),
|
|
60
63
|
* but `config.json` is explicitly "all fields optional" there and this is the natural home for a
|
package/dist/daemon/daemon.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { type DaemonListener } from "./listener.js";
|
|
|
14
14
|
import { type RpcServer } from "./rpc-server.js";
|
|
15
15
|
import { type StateDirPaths } from "./state-dir.js";
|
|
16
16
|
import { type TimerFns } from "./timers.js";
|
|
17
|
+
import { type TlsManager } from "./tls.js";
|
|
17
18
|
/** How often audit retention runs after the startup prune (ARCHITECTURE.md §3: "once a day"). */
|
|
18
19
|
export declare const AUDIT_PRUNE_INTERVAL_MS: number;
|
|
19
20
|
export type DaemonOptions = {
|
|
@@ -33,6 +34,8 @@ export type RunningDaemon = {
|
|
|
33
34
|
startedAt: Date;
|
|
34
35
|
server: RpcServer;
|
|
35
36
|
listener: DaemonListener;
|
|
37
|
+
/** The host certificate the listener serves, so an in-process client can trust it. */
|
|
38
|
+
tls: TlsManager;
|
|
36
39
|
eventBus: EventBus;
|
|
37
40
|
/** Resolves once graceful teardown (sockets closed, RPC/listener closed, pidfile released) completes. */
|
|
38
41
|
exited: Promise<void>;
|