@openacp/cli 2026.406.1 → 2026.406.2
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/{channel-DstweC6V.d.ts → channel-CKXNnTy4.d.ts} +1 -0
- package/dist/cli.js +495 -278
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +29 -14
- package/dist/index.js +403 -201
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -517,6 +517,7 @@ var init_config = __esm({
|
|
|
517
517
|
defaultAgent: z.string(),
|
|
518
518
|
workspace: z.object({
|
|
519
519
|
baseDir: z.string().default("~/openacp-workspace"),
|
|
520
|
+
allowExternalWorkspaces: z.boolean().default(true),
|
|
520
521
|
security: z.object({
|
|
521
522
|
allowedPaths: z.array(z.string()).default([]),
|
|
522
523
|
envWhitelist: z.array(z.string()).default([])
|
|
@@ -637,13 +638,22 @@ var init_config = __esm({
|
|
|
637
638
|
if (input2.startsWith("/") || input2.startsWith("~")) {
|
|
638
639
|
const resolved2 = expandHome3(input2);
|
|
639
640
|
const base = expandHome3(this.config.workspace.baseDir);
|
|
640
|
-
|
|
641
|
-
|
|
641
|
+
const isInternal = resolved2 === base || resolved2.startsWith(base + path4.sep);
|
|
642
|
+
if (!isInternal) {
|
|
643
|
+
if (!this.config.workspace.allowExternalWorkspaces) {
|
|
644
|
+
throw new Error(
|
|
645
|
+
`Workspace path "${input2}" is outside base directory "${this.config.workspace.baseDir}". Set allowExternalWorkspaces: true to allow this.`
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
if (!fs4.existsSync(resolved2)) {
|
|
649
|
+
throw new Error(
|
|
650
|
+
`Workspace path "${input2}" does not exist.`
|
|
651
|
+
);
|
|
652
|
+
}
|
|
642
653
|
return resolved2;
|
|
643
654
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
);
|
|
655
|
+
fs4.mkdirSync(resolved2, { recursive: true });
|
|
656
|
+
return resolved2;
|
|
647
657
|
}
|
|
648
658
|
const name = input2.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
649
659
|
if (name !== input2) {
|
|
@@ -765,6 +775,104 @@ var init_read_text_file = __esm({
|
|
|
765
775
|
}
|
|
766
776
|
});
|
|
767
777
|
|
|
778
|
+
// src/core/events.ts
|
|
779
|
+
var Hook, BusEvent, SessionEv;
|
|
780
|
+
var init_events = __esm({
|
|
781
|
+
"src/core/events.ts"() {
|
|
782
|
+
"use strict";
|
|
783
|
+
Hook = {
|
|
784
|
+
// --- Message flow ---
|
|
785
|
+
/** Incoming message from any adapter — modifiable, can block. */
|
|
786
|
+
MESSAGE_INCOMING: "message:incoming",
|
|
787
|
+
/** Outgoing message before it reaches the adapter — modifiable, can block. */
|
|
788
|
+
MESSAGE_OUTGOING: "message:outgoing",
|
|
789
|
+
// --- Agent / turn lifecycle ---
|
|
790
|
+
/** Before a user prompt is sent to the agent — modifiable, can block. */
|
|
791
|
+
AGENT_BEFORE_PROMPT: "agent:beforePrompt",
|
|
792
|
+
/** Before an agent event is dispatched — modifiable, can block. */
|
|
793
|
+
AGENT_BEFORE_EVENT: "agent:beforeEvent",
|
|
794
|
+
/** After an agent event is dispatched — read-only, fire-and-forget. */
|
|
795
|
+
AGENT_AFTER_EVENT: "agent:afterEvent",
|
|
796
|
+
/** Before the current prompt is cancelled — modifiable, can block. */
|
|
797
|
+
AGENT_BEFORE_CANCEL: "agent:beforeCancel",
|
|
798
|
+
/** Before the agent is switched — modifiable, can block. */
|
|
799
|
+
AGENT_BEFORE_SWITCH: "agent:beforeSwitch",
|
|
800
|
+
/** After the agent has been switched — read-only, fire-and-forget. */
|
|
801
|
+
AGENT_AFTER_SWITCH: "agent:afterSwitch",
|
|
802
|
+
// --- Turn boundaries ---
|
|
803
|
+
/** Turn started — read-only, fire-and-forget. */
|
|
804
|
+
TURN_START: "turn:start",
|
|
805
|
+
/** Turn ended (always fires, even on error) — read-only, fire-and-forget. */
|
|
806
|
+
TURN_END: "turn:end",
|
|
807
|
+
// --- Session lifecycle ---
|
|
808
|
+
/** Before a new session is created — modifiable, can block. */
|
|
809
|
+
SESSION_BEFORE_CREATE: "session:beforeCreate",
|
|
810
|
+
/** After a session is destroyed — read-only, fire-and-forget. */
|
|
811
|
+
SESSION_AFTER_DESTROY: "session:afterDestroy",
|
|
812
|
+
// --- Permissions ---
|
|
813
|
+
/** Before a permission request is shown to the user — modifiable, can block. */
|
|
814
|
+
PERMISSION_BEFORE_REQUEST: "permission:beforeRequest",
|
|
815
|
+
/** After a permission request is resolved — read-only, fire-and-forget. */
|
|
816
|
+
PERMISSION_AFTER_RESOLVE: "permission:afterResolve",
|
|
817
|
+
// --- Config ---
|
|
818
|
+
/** Before config options change — modifiable, can block. */
|
|
819
|
+
CONFIG_BEFORE_CHANGE: "config:beforeChange",
|
|
820
|
+
// --- Filesystem (agent-level) ---
|
|
821
|
+
/** Before a file read operation — modifiable. */
|
|
822
|
+
FS_BEFORE_READ: "fs:beforeRead",
|
|
823
|
+
/** Before a file write operation — modifiable. */
|
|
824
|
+
FS_BEFORE_WRITE: "fs:beforeWrite",
|
|
825
|
+
// --- Terminal ---
|
|
826
|
+
/** Before a terminal session is created — modifiable, can block. */
|
|
827
|
+
TERMINAL_BEFORE_CREATE: "terminal:beforeCreate",
|
|
828
|
+
/** After a terminal session exits — read-only, fire-and-forget. */
|
|
829
|
+
TERMINAL_AFTER_EXIT: "terminal:afterExit"
|
|
830
|
+
};
|
|
831
|
+
BusEvent = {
|
|
832
|
+
// --- Session lifecycle ---
|
|
833
|
+
SESSION_CREATED: "session:created",
|
|
834
|
+
SESSION_UPDATED: "session:updated",
|
|
835
|
+
SESSION_DELETED: "session:deleted",
|
|
836
|
+
SESSION_ENDED: "session:ended",
|
|
837
|
+
SESSION_NAMED: "session:named",
|
|
838
|
+
SESSION_THREAD_READY: "session:threadReady",
|
|
839
|
+
SESSION_CONFIG_CHANGED: "session:configChanged",
|
|
840
|
+
SESSION_AGENT_SWITCH: "session:agentSwitch",
|
|
841
|
+
// --- Agent ---
|
|
842
|
+
AGENT_EVENT: "agent:event",
|
|
843
|
+
AGENT_PROMPT: "agent:prompt",
|
|
844
|
+
// --- Permissions ---
|
|
845
|
+
PERMISSION_REQUEST: "permission:request",
|
|
846
|
+
PERMISSION_RESOLVED: "permission:resolved",
|
|
847
|
+
// --- Message visibility ---
|
|
848
|
+
MESSAGE_QUEUED: "message:queued",
|
|
849
|
+
MESSAGE_PROCESSING: "message:processing",
|
|
850
|
+
// --- System lifecycle ---
|
|
851
|
+
KERNEL_BOOTED: "kernel:booted",
|
|
852
|
+
SYSTEM_READY: "system:ready",
|
|
853
|
+
SYSTEM_SHUTDOWN: "system:shutdown",
|
|
854
|
+
SYSTEM_COMMANDS_READY: "system:commands-ready",
|
|
855
|
+
// --- Plugin lifecycle ---
|
|
856
|
+
PLUGIN_LOADED: "plugin:loaded",
|
|
857
|
+
PLUGIN_FAILED: "plugin:failed",
|
|
858
|
+
PLUGIN_DISABLED: "plugin:disabled",
|
|
859
|
+
PLUGIN_UNLOADED: "plugin:unloaded",
|
|
860
|
+
// --- Usage ---
|
|
861
|
+
USAGE_RECORDED: "usage:recorded"
|
|
862
|
+
};
|
|
863
|
+
SessionEv = {
|
|
864
|
+
AGENT_EVENT: "agent_event",
|
|
865
|
+
PERMISSION_REQUEST: "permission_request",
|
|
866
|
+
SESSION_END: "session_end",
|
|
867
|
+
STATUS_CHANGE: "status_change",
|
|
868
|
+
NAMED: "named",
|
|
869
|
+
ERROR: "error",
|
|
870
|
+
PROMPT_COUNT_CHANGED: "prompt_count_changed",
|
|
871
|
+
TURN_STARTED: "turn_started"
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
|
|
768
876
|
// src/core/utils/bypass-detection.ts
|
|
769
877
|
function isPermissionBypass(value) {
|
|
770
878
|
const lower = value.toLowerCase();
|
|
@@ -3108,6 +3216,7 @@ var MAX_SSE_CONNECTIONS, SSEManager;
|
|
|
3108
3216
|
var init_sse_manager = __esm({
|
|
3109
3217
|
"src/plugins/api-server/sse-manager.ts"() {
|
|
3110
3218
|
"use strict";
|
|
3219
|
+
init_events();
|
|
3111
3220
|
MAX_SSE_CONNECTIONS = 50;
|
|
3112
3221
|
SSEManager = class {
|
|
3113
3222
|
constructor(eventBus, getSessionStats, startedAt) {
|
|
@@ -3122,14 +3231,14 @@ var init_sse_manager = __esm({
|
|
|
3122
3231
|
setup() {
|
|
3123
3232
|
if (!this.eventBus) return;
|
|
3124
3233
|
const events = [
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3234
|
+
BusEvent.SESSION_CREATED,
|
|
3235
|
+
BusEvent.SESSION_UPDATED,
|
|
3236
|
+
BusEvent.SESSION_DELETED,
|
|
3237
|
+
BusEvent.AGENT_EVENT,
|
|
3238
|
+
BusEvent.PERMISSION_REQUEST,
|
|
3239
|
+
BusEvent.PERMISSION_RESOLVED,
|
|
3240
|
+
BusEvent.MESSAGE_QUEUED,
|
|
3241
|
+
BusEvent.MESSAGE_PROCESSING
|
|
3133
3242
|
];
|
|
3134
3243
|
for (const eventName of events) {
|
|
3135
3244
|
const handler = (data) => {
|
|
@@ -3192,12 +3301,12 @@ data: ${JSON.stringify(data)}
|
|
|
3192
3301
|
|
|
3193
3302
|
`;
|
|
3194
3303
|
const sessionEvents = [
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3304
|
+
BusEvent.AGENT_EVENT,
|
|
3305
|
+
BusEvent.PERMISSION_REQUEST,
|
|
3306
|
+
BusEvent.PERMISSION_RESOLVED,
|
|
3307
|
+
BusEvent.SESSION_UPDATED,
|
|
3308
|
+
BusEvent.MESSAGE_QUEUED,
|
|
3309
|
+
BusEvent.MESSAGE_PROCESSING
|
|
3201
3310
|
];
|
|
3202
3311
|
for (const res of this.sseConnections) {
|
|
3203
3312
|
const filter = res.sessionFilter;
|
|
@@ -7318,7 +7427,6 @@ var menu_exports = {};
|
|
|
7318
7427
|
__export(menu_exports, {
|
|
7319
7428
|
buildMenuKeyboard: () => buildMenuKeyboard,
|
|
7320
7429
|
buildSkillMessages: () => buildSkillMessages,
|
|
7321
|
-
handleClear: () => handleClear,
|
|
7322
7430
|
handleHelp: () => handleHelp,
|
|
7323
7431
|
handleMenu: () => handleMenu
|
|
7324
7432
|
});
|
|
@@ -7379,31 +7487,11 @@ Each session gets its own topic \u2014 chat there to work with the agent.
|
|
|
7379
7487
|
/bypass_permissions \u2014 Toggle bypass permissions
|
|
7380
7488
|
/handoff \u2014 Continue session in terminal
|
|
7381
7489
|
/archive \u2014 Archive session topic
|
|
7382
|
-
/clear \u2014 Clear assistant history
|
|
7383
7490
|
|
|
7384
7491
|
\u{1F4AC} Need help? Just ask me in this topic!`,
|
|
7385
7492
|
{ parse_mode: "HTML" }
|
|
7386
7493
|
);
|
|
7387
7494
|
}
|
|
7388
|
-
async function handleClear(ctx, assistant) {
|
|
7389
|
-
if (!assistant) {
|
|
7390
|
-
await ctx.reply("\u26A0\uFE0F Assistant is not available.", { parse_mode: "HTML" });
|
|
7391
|
-
return;
|
|
7392
|
-
}
|
|
7393
|
-
const threadId = ctx.message?.message_thread_id;
|
|
7394
|
-
if (threadId !== assistant.topicId) {
|
|
7395
|
-
await ctx.reply("\u2139\uFE0F /clear only works in the Assistant topic.", { parse_mode: "HTML" });
|
|
7396
|
-
return;
|
|
7397
|
-
}
|
|
7398
|
-
await ctx.reply("\u{1F504} Clearing assistant history...", { parse_mode: "HTML" });
|
|
7399
|
-
try {
|
|
7400
|
-
await assistant.respawn();
|
|
7401
|
-
await ctx.reply("\u2705 Assistant history cleared.", { parse_mode: "HTML" });
|
|
7402
|
-
} catch (err) {
|
|
7403
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
7404
|
-
await ctx.reply(`\u274C Failed to clear: <code>${message}</code>`, { parse_mode: "HTML" });
|
|
7405
|
-
}
|
|
7406
|
-
}
|
|
7407
7495
|
function buildSkillMessages(commands) {
|
|
7408
7496
|
const sorted = [...commands].sort((a, b) => a.name.localeCompare(b.name));
|
|
7409
7497
|
const header2 = "\u{1F6E0} <b>Available Skills</b>\n";
|
|
@@ -8221,7 +8309,6 @@ var init_commands = __esm({
|
|
|
8221
8309
|
{ command: "menu", description: "Show menu" },
|
|
8222
8310
|
{ command: "integrate", description: "Manage agent integrations" },
|
|
8223
8311
|
{ command: "handoff", description: "Continue this session in your terminal" },
|
|
8224
|
-
{ command: "clear", description: "Clear assistant history" },
|
|
8225
8312
|
{ command: "restart", description: "Restart OpenACP" },
|
|
8226
8313
|
{ command: "update", description: "Update to latest version and restart" },
|
|
8227
8314
|
{ command: "doctor", description: "Run system diagnostics" },
|
|
@@ -9312,6 +9399,7 @@ var log35, TelegramAdapter;
|
|
|
9312
9399
|
var init_adapter = __esm({
|
|
9313
9400
|
"src/plugins/telegram/adapter.ts"() {
|
|
9314
9401
|
"use strict";
|
|
9402
|
+
init_events();
|
|
9315
9403
|
init_log();
|
|
9316
9404
|
init_topics();
|
|
9317
9405
|
init_commands();
|
|
@@ -9781,7 +9869,7 @@ ${p}` : p;
|
|
|
9781
9869
|
log35.warn({ err, sessionId }, "Failed to send initial messages for new session");
|
|
9782
9870
|
});
|
|
9783
9871
|
};
|
|
9784
|
-
this.core.eventBus.on(
|
|
9872
|
+
this.core.eventBus.on(BusEvent.SESSION_THREAD_READY, this._threadReadyHandler);
|
|
9785
9873
|
this._configChangedHandler = ({ sessionId }) => {
|
|
9786
9874
|
this.updateControlMessage(sessionId).catch(() => {
|
|
9787
9875
|
});
|
|
@@ -9814,7 +9902,7 @@ ${p}` : p;
|
|
|
9814
9902
|
log35.warn({ err }, "Failed to send welcome message");
|
|
9815
9903
|
}
|
|
9816
9904
|
try {
|
|
9817
|
-
await this.core.assistantManager.
|
|
9905
|
+
await this.core.assistantManager.getOrSpawn("telegram", String(this.assistantTopicId));
|
|
9818
9906
|
} catch (err) {
|
|
9819
9907
|
log35.error({ err }, "Failed to spawn assistant");
|
|
9820
9908
|
}
|
|
@@ -9873,7 +9961,7 @@ OpenACP will automatically retry until this is resolved.`;
|
|
|
9873
9961
|
}
|
|
9874
9962
|
this.sessionTrackers.clear();
|
|
9875
9963
|
if (this._threadReadyHandler) {
|
|
9876
|
-
this.core.eventBus.off(
|
|
9964
|
+
this.core.eventBus.off(BusEvent.SESSION_THREAD_READY, this._threadReadyHandler);
|
|
9877
9965
|
this._threadReadyHandler = void 0;
|
|
9878
9966
|
}
|
|
9879
9967
|
if (this._configChangedHandler) {
|
|
@@ -10943,11 +11031,49 @@ var TypedEmitter = class _TypedEmitter {
|
|
|
10943
11031
|
|
|
10944
11032
|
// src/core/agents/agent-instance.ts
|
|
10945
11033
|
init_read_text_file();
|
|
11034
|
+
|
|
11035
|
+
// src/core/agents/attachment-blocks.ts
|
|
11036
|
+
var SUPPORTED_IMAGE_MIMES = /* @__PURE__ */ new Set([
|
|
11037
|
+
"image/jpeg",
|
|
11038
|
+
"image/png",
|
|
11039
|
+
"image/gif",
|
|
11040
|
+
"image/webp",
|
|
11041
|
+
"image/avif",
|
|
11042
|
+
"image/heic",
|
|
11043
|
+
"image/heif"
|
|
11044
|
+
]);
|
|
11045
|
+
var MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024;
|
|
11046
|
+
function buildAttachmentNote(att, capabilities) {
|
|
11047
|
+
const tooLarge = att.size > MAX_ATTACHMENT_SIZE;
|
|
11048
|
+
if (tooLarge) {
|
|
11049
|
+
const sizeMB = Math.round(att.size / 1024 / 1024);
|
|
11050
|
+
return `[Attachment skipped: "${att.fileName}" is too large (${sizeMB}MB > 10MB limit)]`;
|
|
11051
|
+
}
|
|
11052
|
+
if (att.type === "image") {
|
|
11053
|
+
if (!capabilities.image) {
|
|
11054
|
+
return null;
|
|
11055
|
+
}
|
|
11056
|
+
if (!SUPPORTED_IMAGE_MIMES.has(att.mimeType)) {
|
|
11057
|
+
return `[Attachment skipped: image format not supported (${att.mimeType})]`;
|
|
11058
|
+
}
|
|
11059
|
+
return null;
|
|
11060
|
+
}
|
|
11061
|
+
if (att.type === "audio") {
|
|
11062
|
+
if (!capabilities.audio) {
|
|
11063
|
+
return null;
|
|
11064
|
+
}
|
|
11065
|
+
return null;
|
|
11066
|
+
}
|
|
11067
|
+
return null;
|
|
11068
|
+
}
|
|
11069
|
+
|
|
11070
|
+
// src/core/agents/agent-instance.ts
|
|
10946
11071
|
import { PROTOCOL_VERSION } from "@agentclientprotocol/sdk";
|
|
10947
11072
|
|
|
10948
11073
|
// src/core/sessions/terminal-manager.ts
|
|
10949
11074
|
import { spawn } from "child_process";
|
|
10950
11075
|
import { randomUUID } from "crypto";
|
|
11076
|
+
init_events();
|
|
10951
11077
|
var TerminalManager = class {
|
|
10952
11078
|
terminals = /* @__PURE__ */ new Map();
|
|
10953
11079
|
maxOutputBytes;
|
|
@@ -10964,7 +11090,7 @@ var TerminalManager = class {
|
|
|
10964
11090
|
for (const ev of termEnvArr) {
|
|
10965
11091
|
envRecord[ev.name] = ev.value;
|
|
10966
11092
|
}
|
|
10967
|
-
const result = await middlewareChain.execute(
|
|
11093
|
+
const result = await middlewareChain.execute(Hook.TERMINAL_BEFORE_CREATE, {
|
|
10968
11094
|
sessionId,
|
|
10969
11095
|
command: termCommand,
|
|
10970
11096
|
args: termArgs,
|
|
@@ -11018,7 +11144,7 @@ var TerminalManager = class {
|
|
|
11018
11144
|
childProcess.on("exit", (code, signal) => {
|
|
11019
11145
|
state.exitStatus = { exitCode: code, signal };
|
|
11020
11146
|
if (middlewareChain) {
|
|
11021
|
-
middlewareChain.execute(
|
|
11147
|
+
middlewareChain.execute(Hook.TERMINAL_AFTER_EXIT, {
|
|
11022
11148
|
sessionId,
|
|
11023
11149
|
terminalId,
|
|
11024
11150
|
command: state.command,
|
|
@@ -11148,6 +11274,7 @@ function createDebugTracer(sessionId, workingDirectory) {
|
|
|
11148
11274
|
|
|
11149
11275
|
// src/core/agents/agent-instance.ts
|
|
11150
11276
|
init_log();
|
|
11277
|
+
init_events();
|
|
11151
11278
|
var log4 = createChildLogger({ module: "agent-instance" });
|
|
11152
11279
|
function findPackageRoot(startDir) {
|
|
11153
11280
|
let dir = startDir;
|
|
@@ -11230,7 +11357,7 @@ var AgentInstance = class _AgentInstance extends TypedEmitter {
|
|
|
11230
11357
|
super();
|
|
11231
11358
|
this.agentName = agentName;
|
|
11232
11359
|
}
|
|
11233
|
-
static async spawnSubprocess(agentDef, workingDirectory) {
|
|
11360
|
+
static async spawnSubprocess(agentDef, workingDirectory, allowedPaths = []) {
|
|
11234
11361
|
const instance = new _AgentInstance(agentDef.name);
|
|
11235
11362
|
const resolved = resolveAgentCommand(agentDef.command);
|
|
11236
11363
|
log4.debug(
|
|
@@ -11244,10 +11371,7 @@ var AgentInstance = class _AgentInstance extends TypedEmitter {
|
|
|
11244
11371
|
const ignorePatterns = PathGuard.loadIgnoreFile(workingDirectory);
|
|
11245
11372
|
instance.pathGuard = new PathGuard({
|
|
11246
11373
|
cwd: workingDirectory,
|
|
11247
|
-
|
|
11248
|
-
// spawnSubprocess would need to receive a SecurityConfig param to use it.
|
|
11249
|
-
// Tracked as follow-up: pass workspace security config through spawn/resume call chain.
|
|
11250
|
-
allowedPaths: [],
|
|
11374
|
+
allowedPaths,
|
|
11251
11375
|
ignorePatterns
|
|
11252
11376
|
});
|
|
11253
11377
|
instance.child = spawn2(
|
|
@@ -11341,7 +11465,7 @@ var AgentInstance = class _AgentInstance extends TypedEmitter {
|
|
|
11341
11465
|
if (signal === "SIGINT" || signal === "SIGTERM") return;
|
|
11342
11466
|
if (code !== 0 && code !== null || signal) {
|
|
11343
11467
|
const stderr = this.stderrCapture.getLastLines();
|
|
11344
|
-
this.emit(
|
|
11468
|
+
this.emit(SessionEv.AGENT_EVENT, {
|
|
11345
11469
|
type: "error",
|
|
11346
11470
|
message: signal ? `Agent killed by signal ${signal}
|
|
11347
11471
|
${stderr}` : `Agent crashed (exit code ${code})
|
|
@@ -11353,7 +11477,7 @@ ${stderr}`
|
|
|
11353
11477
|
log4.debug({ sessionId: this.sessionId }, "ACP connection closed");
|
|
11354
11478
|
});
|
|
11355
11479
|
}
|
|
11356
|
-
static async spawn(agentDef, workingDirectory, mcpServers) {
|
|
11480
|
+
static async spawn(agentDef, workingDirectory, mcpServers, allowedPaths) {
|
|
11357
11481
|
log4.debug(
|
|
11358
11482
|
{ agentName: agentDef.name, command: agentDef.command },
|
|
11359
11483
|
"Spawning agent"
|
|
@@ -11361,7 +11485,8 @@ ${stderr}`
|
|
|
11361
11485
|
const spawnStart = Date.now();
|
|
11362
11486
|
const instance = await _AgentInstance.spawnSubprocess(
|
|
11363
11487
|
agentDef,
|
|
11364
|
-
workingDirectory
|
|
11488
|
+
workingDirectory,
|
|
11489
|
+
allowedPaths
|
|
11365
11490
|
);
|
|
11366
11491
|
const resolvedMcp = _AgentInstance.mcpManager.resolve(mcpServers);
|
|
11367
11492
|
const response = await instance.connection.newSession({
|
|
@@ -11384,12 +11509,13 @@ ${stderr}`
|
|
|
11384
11509
|
);
|
|
11385
11510
|
return instance;
|
|
11386
11511
|
}
|
|
11387
|
-
static async resume(agentDef, workingDirectory, agentSessionId, mcpServers) {
|
|
11512
|
+
static async resume(agentDef, workingDirectory, agentSessionId, mcpServers, allowedPaths) {
|
|
11388
11513
|
log4.debug({ agentName: agentDef.name, agentSessionId }, "Resuming agent");
|
|
11389
11514
|
const spawnStart = Date.now();
|
|
11390
11515
|
const instance = await _AgentInstance.spawnSubprocess(
|
|
11391
11516
|
agentDef,
|
|
11392
|
-
workingDirectory
|
|
11517
|
+
workingDirectory,
|
|
11518
|
+
allowedPaths
|
|
11393
11519
|
);
|
|
11394
11520
|
const resolvedMcp = _AgentInstance.mcpManager.resolve(mcpServers);
|
|
11395
11521
|
try {
|
|
@@ -11577,7 +11703,7 @@ ${stderr}`
|
|
|
11577
11703
|
return;
|
|
11578
11704
|
}
|
|
11579
11705
|
if (event !== null) {
|
|
11580
|
-
self.emit(
|
|
11706
|
+
self.emit(SessionEv.AGENT_EVENT, event);
|
|
11581
11707
|
}
|
|
11582
11708
|
},
|
|
11583
11709
|
// ── Permission requests ──────────────────────────────────────────────
|
|
@@ -11604,7 +11730,7 @@ ${stderr}`
|
|
|
11604
11730
|
throw new Error(`[Access denied] ${pathCheck.reason}`);
|
|
11605
11731
|
}
|
|
11606
11732
|
if (self.middlewareChain) {
|
|
11607
|
-
const result = await self.middlewareChain.execute(
|
|
11733
|
+
const result = await self.middlewareChain.execute(Hook.FS_BEFORE_READ, { sessionId: self.sessionId, path: p.path, line: p.line, limit: p.limit }, async (r) => r);
|
|
11608
11734
|
if (!result) return { content: "" };
|
|
11609
11735
|
p.path = result.path;
|
|
11610
11736
|
}
|
|
@@ -11622,7 +11748,7 @@ ${stderr}`
|
|
|
11622
11748
|
throw new Error(`[Access denied] ${pathCheck.reason}`);
|
|
11623
11749
|
}
|
|
11624
11750
|
if (self.middlewareChain) {
|
|
11625
|
-
const result = await self.middlewareChain.execute(
|
|
11751
|
+
const result = await self.middlewareChain.execute(Hook.FS_BEFORE_WRITE, { sessionId: self.sessionId, path: writePath, content: writeContent }, async (r) => r);
|
|
11626
11752
|
if (!result) return {};
|
|
11627
11753
|
writePath = result.path;
|
|
11628
11754
|
writeContent = result.content;
|
|
@@ -11719,10 +11845,16 @@ ${stderr}`
|
|
|
11719
11845
|
// ── Prompt & lifecycle ──────────────────────────────────────────────
|
|
11720
11846
|
async prompt(text3, attachments) {
|
|
11721
11847
|
const contentBlocks = [{ type: "text", text: text3 }];
|
|
11722
|
-
const
|
|
11848
|
+
const capabilities = this.promptCapabilities ?? {};
|
|
11723
11849
|
for (const att of attachments ?? []) {
|
|
11724
|
-
const
|
|
11725
|
-
if (
|
|
11850
|
+
const skipNote = buildAttachmentNote(att, capabilities);
|
|
11851
|
+
if (skipNote !== null) {
|
|
11852
|
+
contentBlocks[0].text += `
|
|
11853
|
+
|
|
11854
|
+
${skipNote}`;
|
|
11855
|
+
continue;
|
|
11856
|
+
}
|
|
11857
|
+
if (att.type === "image" && capabilities.image && SUPPORTED_IMAGE_MIMES.has(att.mimeType)) {
|
|
11726
11858
|
const attCheck = this.pathGuard.validatePath(att.filePath, "read");
|
|
11727
11859
|
if (!attCheck.allowed) {
|
|
11728
11860
|
contentBlocks[0].text += `
|
|
@@ -11732,7 +11864,7 @@ ${stderr}`
|
|
|
11732
11864
|
}
|
|
11733
11865
|
const data = await fs8.promises.readFile(att.filePath);
|
|
11734
11866
|
contentBlocks.push({ type: "image", data: data.toString("base64"), mimeType: att.mimeType });
|
|
11735
|
-
} else if (att.type === "audio" &&
|
|
11867
|
+
} else if (att.type === "audio" && capabilities.audio) {
|
|
11736
11868
|
const attCheck = this.pathGuard.validatePath(att.filePath, "read");
|
|
11737
11869
|
if (!attCheck.allowed) {
|
|
11738
11870
|
contentBlocks[0].text += `
|
|
@@ -11743,9 +11875,9 @@ ${stderr}`
|
|
|
11743
11875
|
const data = await fs8.promises.readFile(att.filePath);
|
|
11744
11876
|
contentBlocks.push({ type: "audio", data: data.toString("base64"), mimeType: att.mimeType });
|
|
11745
11877
|
} else {
|
|
11746
|
-
if (
|
|
11878
|
+
if (att.type === "image" || att.type === "audio") {
|
|
11747
11879
|
log4.debug(
|
|
11748
|
-
{ type: att.type, capabilities
|
|
11880
|
+
{ type: att.type, capabilities },
|
|
11749
11881
|
"Agent does not support %s content, falling back to file path",
|
|
11750
11882
|
att.type
|
|
11751
11883
|
);
|
|
@@ -11801,15 +11933,15 @@ var AgentManager = class {
|
|
|
11801
11933
|
getAgent(name) {
|
|
11802
11934
|
return this.catalog.resolve(name);
|
|
11803
11935
|
}
|
|
11804
|
-
async spawn(agentName, workingDirectory) {
|
|
11936
|
+
async spawn(agentName, workingDirectory, allowedPaths) {
|
|
11805
11937
|
const agentDef = this.getAgent(agentName);
|
|
11806
11938
|
if (!agentDef) throw new Error(`Agent "${agentName}" is not installed. Run "openacp agents install ${agentName}" to add it.`);
|
|
11807
|
-
return AgentInstance.spawn(agentDef, workingDirectory);
|
|
11939
|
+
return AgentInstance.spawn(agentDef, workingDirectory, void 0, allowedPaths);
|
|
11808
11940
|
}
|
|
11809
|
-
async resume(agentName, workingDirectory, agentSessionId) {
|
|
11941
|
+
async resume(agentName, workingDirectory, agentSessionId, allowedPaths) {
|
|
11810
11942
|
const agentDef = this.getAgent(agentName);
|
|
11811
11943
|
if (!agentDef) throw new Error(`Agent "${agentName}" is not installed. Run "openacp agents install ${agentName}" to add it.`);
|
|
11812
|
-
return AgentInstance.resume(agentDef, workingDirectory, agentSessionId);
|
|
11944
|
+
return AgentInstance.resume(agentDef, workingDirectory, agentSessionId, void 0, allowedPaths);
|
|
11813
11945
|
}
|
|
11814
11946
|
};
|
|
11815
11947
|
|
|
@@ -11982,6 +12114,7 @@ function isSystemEvent(event) {
|
|
|
11982
12114
|
}
|
|
11983
12115
|
|
|
11984
12116
|
// src/core/sessions/session.ts
|
|
12117
|
+
init_events();
|
|
11985
12118
|
var moduleLog = createChildLogger({ module: "session" });
|
|
11986
12119
|
var TTS_PROMPT_INSTRUCTION = `
|
|
11987
12120
|
|
|
@@ -12010,7 +12143,15 @@ var Session = class extends TypedEmitter {
|
|
|
12010
12143
|
}
|
|
12011
12144
|
agentName;
|
|
12012
12145
|
workingDirectory;
|
|
12013
|
-
|
|
12146
|
+
_agentInstance;
|
|
12147
|
+
get agentInstance() {
|
|
12148
|
+
return this._agentInstance;
|
|
12149
|
+
}
|
|
12150
|
+
set agentInstance(agent) {
|
|
12151
|
+
this._agentInstance = agent;
|
|
12152
|
+
this.wireAgentRelay();
|
|
12153
|
+
this.wireCommandsBuffer();
|
|
12154
|
+
}
|
|
12014
12155
|
agentSessionId = "";
|
|
12015
12156
|
_status = "initializing";
|
|
12016
12157
|
name;
|
|
@@ -12034,9 +12175,6 @@ var Session = class extends TypedEmitter {
|
|
|
12034
12175
|
threadIds = /* @__PURE__ */ new Map();
|
|
12035
12176
|
/** Active turn context — sealed on prompt dequeue, cleared on turn end */
|
|
12036
12177
|
activeTurnContext = null;
|
|
12037
|
-
/** The agentInstance for which the agent→session event relay is wired (prevents duplicate relays from multiple bridges).
|
|
12038
|
-
* When the agent is swapped, the relay must be re-wired to the new instance. */
|
|
12039
|
-
agentRelaySource = null;
|
|
12040
12178
|
permissionGate = new PermissionGate();
|
|
12041
12179
|
queue;
|
|
12042
12180
|
speechService;
|
|
@@ -12060,23 +12198,37 @@ var Session = class extends TypedEmitter {
|
|
|
12060
12198
|
this.log.error({ err }, "Prompt execution failed");
|
|
12061
12199
|
const message = err instanceof Error ? err.message : String(err);
|
|
12062
12200
|
this.fail(message);
|
|
12063
|
-
this.emit(
|
|
12201
|
+
this.emit(SessionEv.AGENT_EVENT, { type: "error", message: `Prompt execution failed: ${message}` });
|
|
12064
12202
|
}
|
|
12065
12203
|
);
|
|
12066
|
-
|
|
12204
|
+
}
|
|
12205
|
+
/** Wire the agent→session event relay on the current agentInstance.
|
|
12206
|
+
* Removes any previous relay first to avoid duplicates on agent switch.
|
|
12207
|
+
* This relay ensures session.emit("agent_event") fires for ALL sessions,
|
|
12208
|
+
* including headless API sessions that have no SessionBridge attached. */
|
|
12209
|
+
agentRelayCleanup;
|
|
12210
|
+
wireAgentRelay() {
|
|
12211
|
+
this.agentRelayCleanup?.();
|
|
12212
|
+
const instance = this._agentInstance;
|
|
12213
|
+
const handler = (event) => {
|
|
12214
|
+
this.emit(SessionEv.AGENT_EVENT, event);
|
|
12215
|
+
};
|
|
12216
|
+
instance.on(SessionEv.AGENT_EVENT, handler);
|
|
12217
|
+
this.agentRelayCleanup = () => instance.off(SessionEv.AGENT_EVENT, handler);
|
|
12067
12218
|
}
|
|
12068
12219
|
/** Wire a listener on the current agentInstance to buffer commands_update events.
|
|
12069
12220
|
* Must be called after every agentInstance replacement (constructor + switchAgent). */
|
|
12070
12221
|
commandsBufferCleanup;
|
|
12071
12222
|
wireCommandsBuffer() {
|
|
12072
12223
|
this.commandsBufferCleanup?.();
|
|
12224
|
+
const instance = this._agentInstance;
|
|
12073
12225
|
const handler = (event) => {
|
|
12074
12226
|
if (event.type === "commands_update") {
|
|
12075
12227
|
this.latestCommands = event.commands;
|
|
12076
12228
|
}
|
|
12077
12229
|
};
|
|
12078
|
-
|
|
12079
|
-
this.commandsBufferCleanup = () =>
|
|
12230
|
+
instance.on(SessionEv.AGENT_EVENT, handler);
|
|
12231
|
+
this.commandsBufferCleanup = () => instance.off(SessionEv.AGENT_EVENT, handler);
|
|
12080
12232
|
}
|
|
12081
12233
|
// --- State Machine ---
|
|
12082
12234
|
get status() {
|
|
@@ -12090,12 +12242,12 @@ var Session = class extends TypedEmitter {
|
|
|
12090
12242
|
fail(reason) {
|
|
12091
12243
|
if (this._status === "error") return;
|
|
12092
12244
|
this.transition("error");
|
|
12093
|
-
this.emit(
|
|
12245
|
+
this.emit(SessionEv.ERROR, new Error(reason));
|
|
12094
12246
|
}
|
|
12095
12247
|
/** Transition to finished — from active only. Emits session_end for backward compat. */
|
|
12096
12248
|
finish(reason) {
|
|
12097
12249
|
this.transition("finished");
|
|
12098
|
-
this.emit(
|
|
12250
|
+
this.emit(SessionEv.SESSION_END, reason ?? "completed");
|
|
12099
12251
|
}
|
|
12100
12252
|
/** Transition to cancelled — from active only (terminal session cancel) */
|
|
12101
12253
|
markCancelled() {
|
|
@@ -12111,7 +12263,7 @@ var Session = class extends TypedEmitter {
|
|
|
12111
12263
|
}
|
|
12112
12264
|
this._status = to;
|
|
12113
12265
|
this.log.debug({ from, to }, "Session status transition");
|
|
12114
|
-
this.emit(
|
|
12266
|
+
this.emit(SessionEv.STATUS_CHANGE, from, to);
|
|
12115
12267
|
}
|
|
12116
12268
|
/** Number of prompts waiting in queue */
|
|
12117
12269
|
get queueDepth() {
|
|
@@ -12133,8 +12285,8 @@ var Session = class extends TypedEmitter {
|
|
|
12133
12285
|
async enqueuePrompt(text3, attachments, routing, externalTurnId) {
|
|
12134
12286
|
const turnId = externalTurnId ?? nanoid2(8);
|
|
12135
12287
|
if (this.middlewareChain) {
|
|
12136
|
-
const payload = { text: text3, attachments, sessionId: this.id };
|
|
12137
|
-
const result = await this.middlewareChain.execute(
|
|
12288
|
+
const payload = { text: text3, attachments, sessionId: this.id, sourceAdapterId: routing?.sourceAdapterId };
|
|
12289
|
+
const result = await this.middlewareChain.execute(Hook.AGENT_BEFORE_PROMPT, payload, async (p) => p);
|
|
12138
12290
|
if (!result) return turnId;
|
|
12139
12291
|
text3 = result.text;
|
|
12140
12292
|
attachments = result.attachments;
|
|
@@ -12149,9 +12301,9 @@ var Session = class extends TypedEmitter {
|
|
|
12149
12301
|
routing?.responseAdapterId,
|
|
12150
12302
|
turnId
|
|
12151
12303
|
);
|
|
12152
|
-
this.emit(
|
|
12304
|
+
this.emit(SessionEv.TURN_STARTED, this.activeTurnContext);
|
|
12153
12305
|
this.promptCount++;
|
|
12154
|
-
this.emit(
|
|
12306
|
+
this.emit(SessionEv.PROMPT_COUNT_CHANGED, this.promptCount);
|
|
12155
12307
|
if (this._status === "initializing" || this._status === "cancelled" || this._status === "error") {
|
|
12156
12308
|
this.activate();
|
|
12157
12309
|
}
|
|
@@ -12180,10 +12332,18 @@ ${text3}`;
|
|
|
12180
12332
|
}
|
|
12181
12333
|
} : null;
|
|
12182
12334
|
if (accumulatorListener) {
|
|
12183
|
-
this.on(
|
|
12335
|
+
this.on(SessionEv.AGENT_EVENT, accumulatorListener);
|
|
12336
|
+
}
|
|
12337
|
+
const mw = this.middlewareChain;
|
|
12338
|
+
const afterEventListener = mw ? (event) => {
|
|
12339
|
+
mw.execute(Hook.AGENT_AFTER_EVENT, { sessionId: this.id, event, outgoingMessage: { type: "text", text: "" } }, async (e) => e).catch(() => {
|
|
12340
|
+
});
|
|
12341
|
+
} : null;
|
|
12342
|
+
if (afterEventListener) {
|
|
12343
|
+
this.agentInstance.on(SessionEv.AGENT_EVENT, afterEventListener);
|
|
12184
12344
|
}
|
|
12185
12345
|
if (this.middlewareChain) {
|
|
12186
|
-
this.middlewareChain.execute(
|
|
12346
|
+
this.middlewareChain.execute(Hook.TURN_START, { sessionId: this.id, promptText: processed.text, promptNumber: this.promptCount }, async (p) => p).catch(() => {
|
|
12187
12347
|
});
|
|
12188
12348
|
}
|
|
12189
12349
|
let stopReason = "end_turn";
|
|
@@ -12204,10 +12364,13 @@ ${text3}`;
|
|
|
12204
12364
|
promptError = err;
|
|
12205
12365
|
} finally {
|
|
12206
12366
|
if (accumulatorListener) {
|
|
12207
|
-
this.off(
|
|
12367
|
+
this.off(SessionEv.AGENT_EVENT, accumulatorListener);
|
|
12368
|
+
}
|
|
12369
|
+
if (afterEventListener) {
|
|
12370
|
+
this.agentInstance.off(SessionEv.AGENT_EVENT, afterEventListener);
|
|
12208
12371
|
}
|
|
12209
12372
|
if (this.middlewareChain) {
|
|
12210
|
-
this.middlewareChain.execute(
|
|
12373
|
+
this.middlewareChain.execute(Hook.TURN_END, { sessionId: this.id, stopReason, durationMs: Date.now() - promptStart }, async (p) => p).catch(() => {
|
|
12211
12374
|
});
|
|
12212
12375
|
}
|
|
12213
12376
|
this.activeTurnContext = null;
|
|
@@ -12252,7 +12415,7 @@ ${text3}`;
|
|
|
12252
12415
|
const audioBuffer = await fs9.promises.readFile(audioPath);
|
|
12253
12416
|
const result = await this.speechService.transcribe(audioBuffer, audioMime);
|
|
12254
12417
|
this.log.info({ provider: "stt", duration: result.duration }, "Voice transcribed");
|
|
12255
|
-
this.emit(
|
|
12418
|
+
this.emit(SessionEv.AGENT_EVENT, {
|
|
12256
12419
|
type: "system_message",
|
|
12257
12420
|
message: `\u{1F3A4} You said: ${result.text}`
|
|
12258
12421
|
});
|
|
@@ -12261,7 +12424,7 @@ ${text3}`;
|
|
|
12261
12424
|
${result.text}` : result.text;
|
|
12262
12425
|
} catch (err) {
|
|
12263
12426
|
this.log.warn({ err }, "STT transcription failed, keeping audio attachment");
|
|
12264
|
-
this.emit(
|
|
12427
|
+
this.emit(SessionEv.AGENT_EVENT, {
|
|
12265
12428
|
type: "error",
|
|
12266
12429
|
message: `Voice transcription failed: ${err.message}`
|
|
12267
12430
|
});
|
|
@@ -12295,12 +12458,12 @@ ${result.text}` : result.text;
|
|
|
12295
12458
|
timeoutPromise
|
|
12296
12459
|
]);
|
|
12297
12460
|
const base64 = result.audioBuffer.toString("base64");
|
|
12298
|
-
this.emit(
|
|
12461
|
+
this.emit(SessionEv.AGENT_EVENT, {
|
|
12299
12462
|
type: "audio_content",
|
|
12300
12463
|
data: base64,
|
|
12301
12464
|
mimeType: result.mimeType
|
|
12302
12465
|
});
|
|
12303
|
-
this.emit(
|
|
12466
|
+
this.emit(SessionEv.AGENT_EVENT, { type: "tts_strip" });
|
|
12304
12467
|
this.log.info("TTS synthesis completed");
|
|
12305
12468
|
} finally {
|
|
12306
12469
|
clearTimeout(ttsTimer);
|
|
@@ -12315,19 +12478,19 @@ ${result.text}` : result.text;
|
|
|
12315
12478
|
const captureHandler = (event) => {
|
|
12316
12479
|
if (event.type === "text") title += event.content;
|
|
12317
12480
|
};
|
|
12318
|
-
this.pause((event) => event !==
|
|
12319
|
-
this.agentInstance.on(
|
|
12481
|
+
this.pause((event) => event !== SessionEv.AGENT_EVENT);
|
|
12482
|
+
this.agentInstance.on(SessionEv.AGENT_EVENT, captureHandler);
|
|
12320
12483
|
try {
|
|
12321
12484
|
await this.agentInstance.prompt(
|
|
12322
12485
|
"Summarize this conversation in max 5 words for a topic title. Reply ONLY with the title, nothing else."
|
|
12323
12486
|
);
|
|
12324
12487
|
this.name = title.trim().slice(0, 50) || `Session ${this.id.slice(0, 6)}`;
|
|
12325
12488
|
this.log.info({ name: this.name }, "Session auto-named");
|
|
12326
|
-
this.emit(
|
|
12489
|
+
this.emit(SessionEv.NAMED, this.name);
|
|
12327
12490
|
} catch {
|
|
12328
12491
|
this.name = `Session ${this.id.slice(0, 6)}`;
|
|
12329
12492
|
} finally {
|
|
12330
|
-
this.agentInstance.off(
|
|
12493
|
+
this.agentInstance.off(SessionEv.AGENT_EVENT, captureHandler);
|
|
12331
12494
|
this.clearBuffer();
|
|
12332
12495
|
this.resume();
|
|
12333
12496
|
}
|
|
@@ -12389,7 +12552,7 @@ ${result.text}` : result.text;
|
|
|
12389
12552
|
/** Set session name explicitly and emit 'named' event */
|
|
12390
12553
|
setName(name) {
|
|
12391
12554
|
this.name = name;
|
|
12392
|
-
this.emit(
|
|
12555
|
+
this.emit(SessionEv.NAMED, name);
|
|
12393
12556
|
}
|
|
12394
12557
|
/** Send a config option change to the agent and update local state from the response. */
|
|
12395
12558
|
async setConfigOption(configId, value) {
|
|
@@ -12405,7 +12568,7 @@ ${result.text}` : result.text;
|
|
|
12405
12568
|
}
|
|
12406
12569
|
async updateConfigOptions(options) {
|
|
12407
12570
|
if (this.middlewareChain) {
|
|
12408
|
-
const result = await this.middlewareChain.execute(
|
|
12571
|
+
const result = await this.middlewareChain.execute(Hook.CONFIG_BEFORE_CHANGE, { sessionId: this.id, configId: "options", oldValue: this.configOptions, newValue: options }, async (p) => p);
|
|
12409
12572
|
if (!result) return;
|
|
12410
12573
|
}
|
|
12411
12574
|
this.configOptions = options;
|
|
@@ -12425,7 +12588,7 @@ ${result.text}` : result.text;
|
|
|
12425
12588
|
/** Cancel the current prompt and clear the queue. Stays in active state. */
|
|
12426
12589
|
async abortPrompt() {
|
|
12427
12590
|
if (this.middlewareChain) {
|
|
12428
|
-
const result = await this.middlewareChain.execute(
|
|
12591
|
+
const result = await this.middlewareChain.execute(Hook.AGENT_BEFORE_CANCEL, { sessionId: this.id }, async (p) => p);
|
|
12429
12592
|
if (!result) return;
|
|
12430
12593
|
}
|
|
12431
12594
|
this.queue.clear();
|
|
@@ -12466,7 +12629,6 @@ ${result.text}` : result.text;
|
|
|
12466
12629
|
this.configOptions = [];
|
|
12467
12630
|
this.latestCommands = null;
|
|
12468
12631
|
this.applySpawnResponse(newAgent.initialSessionResponse, newAgent.agentCapabilities);
|
|
12469
|
-
this.wireCommandsBuffer();
|
|
12470
12632
|
this.log.info({ from: this.agentSwitchHistory.at(-1).agentName, to: agentName }, "Agent switched");
|
|
12471
12633
|
}
|
|
12472
12634
|
async destroy() {
|
|
@@ -12905,6 +13067,7 @@ var MessageTransformer = class {
|
|
|
12905
13067
|
};
|
|
12906
13068
|
|
|
12907
13069
|
// src/core/sessions/session-manager.ts
|
|
13070
|
+
init_events();
|
|
12908
13071
|
var SessionManager = class {
|
|
12909
13072
|
sessions = /* @__PURE__ */ new Map();
|
|
12910
13073
|
store;
|
|
@@ -13009,18 +13172,18 @@ var SessionManager = class {
|
|
|
13009
13172
|
}
|
|
13010
13173
|
}
|
|
13011
13174
|
if (this.middlewareChain) {
|
|
13012
|
-
this.middlewareChain.execute(
|
|
13175
|
+
this.middlewareChain.execute(Hook.SESSION_AFTER_DESTROY, { sessionId }, async (p) => p).catch(() => {
|
|
13013
13176
|
});
|
|
13014
13177
|
}
|
|
13015
13178
|
}
|
|
13016
13179
|
listSessions(channelId) {
|
|
13017
|
-
const all = Array.from(this.sessions.values());
|
|
13180
|
+
const all = Array.from(this.sessions.values()).filter((s) => !s.isAssistant);
|
|
13018
13181
|
if (channelId) return all.filter((s) => s.channelId === channelId);
|
|
13019
13182
|
return all;
|
|
13020
13183
|
}
|
|
13021
13184
|
listAllSessions(channelId) {
|
|
13022
13185
|
if (this.store) {
|
|
13023
|
-
let records = this.store.list();
|
|
13186
|
+
let records = this.store.list().filter((r) => !r.isAssistant);
|
|
13024
13187
|
if (channelId) records = records.filter((r) => r.channelId === channelId);
|
|
13025
13188
|
return records.map((record) => {
|
|
13026
13189
|
const live2 = this.sessions.get(record.sessionId);
|
|
@@ -13060,7 +13223,7 @@ var SessionManager = class {
|
|
|
13060
13223
|
};
|
|
13061
13224
|
});
|
|
13062
13225
|
}
|
|
13063
|
-
let live = Array.from(this.sessions.values());
|
|
13226
|
+
let live = Array.from(this.sessions.values()).filter((s) => !s.isAssistant);
|
|
13064
13227
|
if (channelId) live = live.filter((s) => s.channelId === channelId);
|
|
13065
13228
|
return live.map((s) => ({
|
|
13066
13229
|
id: s.id,
|
|
@@ -13081,7 +13244,7 @@ var SessionManager = class {
|
|
|
13081
13244
|
}
|
|
13082
13245
|
listRecords(filter) {
|
|
13083
13246
|
if (!this.store) return [];
|
|
13084
|
-
let records = this.store.list();
|
|
13247
|
+
let records = this.store.list().filter((r) => !r.isAssistant);
|
|
13085
13248
|
if (filter?.statuses?.length) {
|
|
13086
13249
|
records = records.filter((r) => filter.statuses.includes(r.status));
|
|
13087
13250
|
}
|
|
@@ -13090,7 +13253,7 @@ var SessionManager = class {
|
|
|
13090
13253
|
async removeRecord(sessionId) {
|
|
13091
13254
|
if (!this.store) return;
|
|
13092
13255
|
await this.store.remove(sessionId);
|
|
13093
|
-
this.eventBus?.emit(
|
|
13256
|
+
this.eventBus?.emit(BusEvent.SESSION_DELETED, { sessionId });
|
|
13094
13257
|
}
|
|
13095
13258
|
/**
|
|
13096
13259
|
* Graceful shutdown: persist session state without killing agent subprocesses.
|
|
@@ -13138,7 +13301,7 @@ var SessionManager = class {
|
|
|
13138
13301
|
this.sessions.clear();
|
|
13139
13302
|
if (this.middlewareChain) {
|
|
13140
13303
|
for (const sessionId of sessionIds) {
|
|
13141
|
-
this.middlewareChain.execute(
|
|
13304
|
+
this.middlewareChain.execute(Hook.SESSION_AFTER_DESTROY, { sessionId }, async (p) => p).catch(() => {
|
|
13142
13305
|
});
|
|
13143
13306
|
}
|
|
13144
13307
|
}
|
|
@@ -13148,6 +13311,7 @@ var SessionManager = class {
|
|
|
13148
13311
|
// src/core/sessions/session-bridge.ts
|
|
13149
13312
|
init_log();
|
|
13150
13313
|
init_bypass_detection();
|
|
13314
|
+
init_events();
|
|
13151
13315
|
var log6 = createChildLogger({ module: "session-bridge" });
|
|
13152
13316
|
var SessionBridge = class {
|
|
13153
13317
|
constructor(session, adapter, deps, adapterId) {
|
|
@@ -13172,7 +13336,7 @@ var SessionBridge = class {
|
|
|
13172
13336
|
try {
|
|
13173
13337
|
const mw = this.deps.middlewareChain;
|
|
13174
13338
|
if (mw) {
|
|
13175
|
-
const result = await mw.execute(
|
|
13339
|
+
const result = await mw.execute(Hook.MESSAGE_OUTGOING, { sessionId, message }, async (m) => m);
|
|
13176
13340
|
this.tracer?.log("core", { step: "middleware:outgoing", sessionId, hook: "message:outgoing", blocked: !result });
|
|
13177
13341
|
if (!result) return;
|
|
13178
13342
|
this.tracer?.log("core", { step: "dispatch", sessionId, message: result.message });
|
|
@@ -13201,17 +13365,11 @@ var SessionBridge = class {
|
|
|
13201
13365
|
connect() {
|
|
13202
13366
|
if (this.connected) return;
|
|
13203
13367
|
this.connected = true;
|
|
13204
|
-
|
|
13205
|
-
this.listen(this.session.agentInstance, "agent_event", (event) => {
|
|
13206
|
-
this.session.emit("agent_event", event);
|
|
13207
|
-
});
|
|
13208
|
-
this.session.agentRelaySource = this.session.agentInstance;
|
|
13209
|
-
}
|
|
13210
|
-
this.listen(this.session, "agent_event", (event) => {
|
|
13368
|
+
this.listen(this.session, SessionEv.AGENT_EVENT, (event) => {
|
|
13211
13369
|
if (this.shouldForward(event)) {
|
|
13212
13370
|
this.dispatchAgentEvent(event);
|
|
13213
13371
|
} else {
|
|
13214
|
-
this.deps.eventBus?.emit(
|
|
13372
|
+
this.deps.eventBus?.emit(BusEvent.AGENT_EVENT, { sessionId: this.session.id, event });
|
|
13215
13373
|
}
|
|
13216
13374
|
});
|
|
13217
13375
|
if (!this.session.agentInstance.onPermissionRequest || this.session.agentInstance.onPermissionRequest.__bridgeId === void 0) {
|
|
@@ -13221,7 +13379,7 @@ var SessionBridge = class {
|
|
|
13221
13379
|
handler.__bridgeId = this.adapterId;
|
|
13222
13380
|
this.session.agentInstance.onPermissionRequest = handler;
|
|
13223
13381
|
}
|
|
13224
|
-
this.listen(this.session,
|
|
13382
|
+
this.listen(this.session, SessionEv.PERMISSION_REQUEST, async (request) => {
|
|
13225
13383
|
const current = this.session.agentInstance.onPermissionRequest;
|
|
13226
13384
|
if (current?.__bridgeId === this.adapterId) return;
|
|
13227
13385
|
if (!this.session.permissionGate.isPending) return;
|
|
@@ -13231,37 +13389,41 @@ var SessionBridge = class {
|
|
|
13231
13389
|
log6.error({ err, sessionId: this.session.id, adapterId: this.adapterId }, "Failed to send permission request to adapter");
|
|
13232
13390
|
}
|
|
13233
13391
|
});
|
|
13234
|
-
this.listen(this.session,
|
|
13392
|
+
this.listen(this.session, SessionEv.STATUS_CHANGE, (from, to) => {
|
|
13235
13393
|
this.deps.sessionManager.patchRecord(this.session.id, {
|
|
13236
13394
|
status: to,
|
|
13237
13395
|
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13238
13396
|
});
|
|
13239
|
-
this.
|
|
13240
|
-
|
|
13241
|
-
|
|
13242
|
-
|
|
13397
|
+
if (!this.session.isAssistant) {
|
|
13398
|
+
this.deps.eventBus?.emit(BusEvent.SESSION_UPDATED, {
|
|
13399
|
+
sessionId: this.session.id,
|
|
13400
|
+
status: to
|
|
13401
|
+
});
|
|
13402
|
+
}
|
|
13243
13403
|
if (to === "finished") {
|
|
13244
13404
|
queueMicrotask(() => this.disconnect());
|
|
13245
13405
|
}
|
|
13246
13406
|
});
|
|
13247
|
-
this.listen(this.session,
|
|
13407
|
+
this.listen(this.session, SessionEv.NAMED, async (name) => {
|
|
13248
13408
|
const record = this.deps.sessionManager.getSessionRecord(this.session.id);
|
|
13249
13409
|
const alreadyNamed = !!record?.name;
|
|
13250
13410
|
await this.deps.sessionManager.patchRecord(this.session.id, { name });
|
|
13251
|
-
this.
|
|
13252
|
-
|
|
13253
|
-
|
|
13254
|
-
|
|
13411
|
+
if (!this.session.isAssistant) {
|
|
13412
|
+
this.deps.eventBus?.emit(BusEvent.SESSION_UPDATED, {
|
|
13413
|
+
sessionId: this.session.id,
|
|
13414
|
+
name
|
|
13415
|
+
});
|
|
13416
|
+
}
|
|
13255
13417
|
if (!alreadyNamed) {
|
|
13256
13418
|
await this.adapter.renameSessionThread(this.session.id, name);
|
|
13257
13419
|
}
|
|
13258
13420
|
});
|
|
13259
|
-
this.listen(this.session,
|
|
13421
|
+
this.listen(this.session, SessionEv.PROMPT_COUNT_CHANGED, (count) => {
|
|
13260
13422
|
this.deps.sessionManager.patchRecord(this.session.id, { currentPromptCount: count });
|
|
13261
13423
|
});
|
|
13262
|
-
this.listen(this.session,
|
|
13424
|
+
this.listen(this.session, SessionEv.TURN_STARTED, (ctx) => {
|
|
13263
13425
|
if (ctx.sourceAdapterId !== "sse" && ctx.sourceAdapterId !== "api") {
|
|
13264
|
-
this.deps.eventBus?.emit(
|
|
13426
|
+
this.deps.eventBus?.emit(BusEvent.MESSAGE_PROCESSING, {
|
|
13265
13427
|
sessionId: this.session.id,
|
|
13266
13428
|
turnId: ctx.turnId,
|
|
13267
13429
|
sourceAdapterId: ctx.sourceAdapterId,
|
|
@@ -13270,10 +13432,10 @@ var SessionBridge = class {
|
|
|
13270
13432
|
}
|
|
13271
13433
|
});
|
|
13272
13434
|
if (this.session.latestCommands !== null) {
|
|
13273
|
-
this.session.emit(
|
|
13435
|
+
this.session.emit(SessionEv.AGENT_EVENT, { type: "commands_update", commands: this.session.latestCommands });
|
|
13274
13436
|
}
|
|
13275
13437
|
if (this.session.configOptions.length > 0) {
|
|
13276
|
-
this.session.emit(
|
|
13438
|
+
this.session.emit(SessionEv.AGENT_EVENT, { type: "config_option_update", options: this.session.configOptions });
|
|
13277
13439
|
}
|
|
13278
13440
|
}
|
|
13279
13441
|
disconnect() {
|
|
@@ -13293,17 +13455,11 @@ var SessionBridge = class {
|
|
|
13293
13455
|
const mw = this.deps.middlewareChain;
|
|
13294
13456
|
if (mw) {
|
|
13295
13457
|
try {
|
|
13296
|
-
const result = await mw.execute(
|
|
13458
|
+
const result = await mw.execute(Hook.AGENT_BEFORE_EVENT, { sessionId: this.session.id, event }, async (e) => e);
|
|
13297
13459
|
this.tracer?.log("core", { step: "middleware:before", sessionId: this.session.id, hook: "agent:beforeEvent", blocked: !result });
|
|
13298
13460
|
if (!result) return;
|
|
13299
13461
|
const transformedEvent = result.event;
|
|
13300
|
-
|
|
13301
|
-
mw.execute("agent:afterEvent", {
|
|
13302
|
-
sessionId: this.session.id,
|
|
13303
|
-
event: transformedEvent,
|
|
13304
|
-
outgoingMessage: outgoing ?? { type: "text", text: "" }
|
|
13305
|
-
}, async (e) => e).catch(() => {
|
|
13306
|
-
});
|
|
13462
|
+
this.handleAgentEvent(transformedEvent);
|
|
13307
13463
|
} catch {
|
|
13308
13464
|
try {
|
|
13309
13465
|
this.handleAgentEvent(event);
|
|
@@ -13436,7 +13592,7 @@ var SessionBridge = class {
|
|
|
13436
13592
|
this.adapter.stripTTSBlock?.(this.session.id);
|
|
13437
13593
|
break;
|
|
13438
13594
|
}
|
|
13439
|
-
this.deps.eventBus?.emit(
|
|
13595
|
+
this.deps.eventBus?.emit(BusEvent.AGENT_EVENT, {
|
|
13440
13596
|
sessionId: this.session.id,
|
|
13441
13597
|
event
|
|
13442
13598
|
});
|
|
@@ -13455,7 +13611,7 @@ var SessionBridge = class {
|
|
|
13455
13611
|
let permReq = request;
|
|
13456
13612
|
if (mw) {
|
|
13457
13613
|
const payload = { sessionId: this.session.id, request, autoResolve: void 0 };
|
|
13458
|
-
const result = await mw.execute(
|
|
13614
|
+
const result = await mw.execute(Hook.PERMISSION_BEFORE_REQUEST, payload, async (r) => r);
|
|
13459
13615
|
if (!result) return "";
|
|
13460
13616
|
permReq = result.request;
|
|
13461
13617
|
if (result.autoResolve) {
|
|
@@ -13463,21 +13619,21 @@ var SessionBridge = class {
|
|
|
13463
13619
|
return result.autoResolve;
|
|
13464
13620
|
}
|
|
13465
13621
|
}
|
|
13466
|
-
this.deps.eventBus?.emit(
|
|
13622
|
+
this.deps.eventBus?.emit(BusEvent.PERMISSION_REQUEST, {
|
|
13467
13623
|
sessionId: this.session.id,
|
|
13468
13624
|
permission: permReq
|
|
13469
13625
|
});
|
|
13470
13626
|
const autoDecision = this.checkAutoApprove(permReq);
|
|
13471
13627
|
if (autoDecision) {
|
|
13472
|
-
this.session.emit(
|
|
13628
|
+
this.session.emit(SessionEv.PERMISSION_REQUEST, permReq);
|
|
13473
13629
|
this.emitAfterResolve(mw, permReq.id, autoDecision, "system", startTime);
|
|
13474
13630
|
return autoDecision;
|
|
13475
13631
|
}
|
|
13476
13632
|
const promise = this.session.permissionGate.setPending(permReq);
|
|
13477
|
-
this.session.emit(
|
|
13633
|
+
this.session.emit(SessionEv.PERMISSION_REQUEST, permReq);
|
|
13478
13634
|
await this.adapter.sendPermissionRequest(this.session.id, permReq);
|
|
13479
13635
|
const optionId = await promise;
|
|
13480
|
-
this.deps.eventBus?.emit(
|
|
13636
|
+
this.deps.eventBus?.emit(BusEvent.PERMISSION_RESOLVED, {
|
|
13481
13637
|
sessionId: this.session.id,
|
|
13482
13638
|
requestId: permReq.id,
|
|
13483
13639
|
decision: optionId,
|
|
@@ -13509,7 +13665,7 @@ var SessionBridge = class {
|
|
|
13509
13665
|
/** Emit permission:afterResolve middleware hook (fire-and-forget) */
|
|
13510
13666
|
emitAfterResolve(mw, requestId, decision, userId, startTime) {
|
|
13511
13667
|
if (mw) {
|
|
13512
|
-
mw.execute(
|
|
13668
|
+
mw.execute(Hook.PERMISSION_AFTER_RESOLVE, {
|
|
13513
13669
|
sessionId: this.session.id,
|
|
13514
13670
|
requestId,
|
|
13515
13671
|
decision,
|
|
@@ -13523,6 +13679,7 @@ var SessionBridge = class {
|
|
|
13523
13679
|
|
|
13524
13680
|
// src/core/sessions/session-factory.ts
|
|
13525
13681
|
init_log();
|
|
13682
|
+
init_events();
|
|
13526
13683
|
var log7 = createChildLogger({ module: "session-factory" });
|
|
13527
13684
|
var SessionFactory = class {
|
|
13528
13685
|
constructor(agentManager, sessionManager, speechServiceAccessor, eventBus, instanceRoot) {
|
|
@@ -13565,7 +13722,7 @@ var SessionFactory = class {
|
|
|
13565
13722
|
threadId: ""
|
|
13566
13723
|
// threadId is assigned after session creation
|
|
13567
13724
|
};
|
|
13568
|
-
const result = await this.middlewareChain.execute(
|
|
13725
|
+
const result = await this.middlewareChain.execute(Hook.SESSION_BEFORE_CREATE, payload, async (p) => p);
|
|
13569
13726
|
if (!result) throw new Error("Session creation blocked by middleware");
|
|
13570
13727
|
createParams = {
|
|
13571
13728
|
...params,
|
|
@@ -13574,6 +13731,7 @@ var SessionFactory = class {
|
|
|
13574
13731
|
channelId: result.channelId
|
|
13575
13732
|
};
|
|
13576
13733
|
}
|
|
13734
|
+
const configAllowedPaths = this.configManager?.get().workspace?.security?.allowedPaths ?? [];
|
|
13577
13735
|
let agentInstance;
|
|
13578
13736
|
try {
|
|
13579
13737
|
if (createParams.resumeAgentSessionId) {
|
|
@@ -13581,7 +13739,8 @@ var SessionFactory = class {
|
|
|
13581
13739
|
agentInstance = await this.agentManager.resume(
|
|
13582
13740
|
createParams.agentName,
|
|
13583
13741
|
createParams.workingDirectory,
|
|
13584
|
-
createParams.resumeAgentSessionId
|
|
13742
|
+
createParams.resumeAgentSessionId,
|
|
13743
|
+
configAllowedPaths
|
|
13585
13744
|
);
|
|
13586
13745
|
} catch (resumeErr) {
|
|
13587
13746
|
log7.warn(
|
|
@@ -13590,13 +13749,15 @@ var SessionFactory = class {
|
|
|
13590
13749
|
);
|
|
13591
13750
|
agentInstance = await this.agentManager.spawn(
|
|
13592
13751
|
createParams.agentName,
|
|
13593
|
-
createParams.workingDirectory
|
|
13752
|
+
createParams.workingDirectory,
|
|
13753
|
+
configAllowedPaths
|
|
13594
13754
|
);
|
|
13595
13755
|
}
|
|
13596
13756
|
} else {
|
|
13597
13757
|
agentInstance = await this.agentManager.spawn(
|
|
13598
13758
|
createParams.agentName,
|
|
13599
|
-
createParams.workingDirectory
|
|
13759
|
+
createParams.workingDirectory,
|
|
13760
|
+
configAllowedPaths
|
|
13600
13761
|
);
|
|
13601
13762
|
}
|
|
13602
13763
|
} catch (err) {
|
|
@@ -13628,7 +13789,7 @@ var SessionFactory = class {
|
|
|
13628
13789
|
message: guidanceLines.join("\n")
|
|
13629
13790
|
};
|
|
13630
13791
|
const failedSessionId = createParams.existingSessionId ?? `failed-${Date.now()}`;
|
|
13631
|
-
this.eventBus.emit(
|
|
13792
|
+
this.eventBus.emit(BusEvent.AGENT_EVENT, {
|
|
13632
13793
|
sessionId: failedSessionId,
|
|
13633
13794
|
event: guidance
|
|
13634
13795
|
});
|
|
@@ -13654,11 +13815,13 @@ var SessionFactory = class {
|
|
|
13654
13815
|
}
|
|
13655
13816
|
session.applySpawnResponse(agentInstance.initialSessionResponse, agentInstance.agentCapabilities);
|
|
13656
13817
|
this.sessionManager.registerSession(session);
|
|
13657
|
-
|
|
13658
|
-
|
|
13659
|
-
|
|
13660
|
-
|
|
13661
|
-
|
|
13818
|
+
if (!session.isAssistant) {
|
|
13819
|
+
this.eventBus.emit(BusEvent.SESSION_CREATED, {
|
|
13820
|
+
sessionId: session.id,
|
|
13821
|
+
agent: session.agentName,
|
|
13822
|
+
status: session.status
|
|
13823
|
+
});
|
|
13824
|
+
}
|
|
13662
13825
|
return session;
|
|
13663
13826
|
}
|
|
13664
13827
|
/**
|
|
@@ -13676,6 +13839,7 @@ var SessionFactory = class {
|
|
|
13676
13839
|
if (!this.sessionStore || !this.createFullSession) return null;
|
|
13677
13840
|
const record = this.sessionStore.get(sessionId);
|
|
13678
13841
|
if (!record) return null;
|
|
13842
|
+
if (record.isAssistant) return null;
|
|
13679
13843
|
if (record.status === "error" || record.status === "cancelled") return null;
|
|
13680
13844
|
const existing = this.resumeLocks.get(sessionId);
|
|
13681
13845
|
if (existing) return existing;
|
|
@@ -13743,6 +13907,7 @@ var SessionFactory = class {
|
|
|
13743
13907
|
log7.debug({ threadId, channelId }, "No session record found for thread");
|
|
13744
13908
|
return null;
|
|
13745
13909
|
}
|
|
13910
|
+
if (record.isAssistant) return null;
|
|
13746
13911
|
if (record.status === "error" || record.status === "cancelled") {
|
|
13747
13912
|
log7.warn(
|
|
13748
13913
|
{ threadId, sessionId: record.sessionId, status: record.status },
|
|
@@ -13902,9 +14067,9 @@ var SessionFactory = class {
|
|
|
13902
14067
|
return { session, contextResult };
|
|
13903
14068
|
}
|
|
13904
14069
|
wireSideEffects(session, deps) {
|
|
13905
|
-
session.on(
|
|
14070
|
+
session.on(SessionEv.AGENT_EVENT, (event) => {
|
|
13906
14071
|
if (event.type !== "usage") return;
|
|
13907
|
-
deps.eventBus.emit(
|
|
14072
|
+
deps.eventBus.emit(BusEvent.USAGE_RECORDED, {
|
|
13908
14073
|
sessionId: session.id,
|
|
13909
14074
|
agentName: session.agentName,
|
|
13910
14075
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -13913,7 +14078,7 @@ var SessionFactory = class {
|
|
|
13913
14078
|
cost: event.cost
|
|
13914
14079
|
});
|
|
13915
14080
|
});
|
|
13916
|
-
session.on(
|
|
14081
|
+
session.on(SessionEv.STATUS_CHANGE, (_from, to) => {
|
|
13917
14082
|
if ((to === "finished" || to === "cancelled") && deps.tunnelService) {
|
|
13918
14083
|
deps.tunnelService.stopBySession(session.id).then((stopped) => {
|
|
13919
14084
|
for (const entry of stopped) {
|
|
@@ -13993,6 +14158,14 @@ var JsonFileSessionStore = class {
|
|
|
13993
14158
|
}
|
|
13994
14159
|
return void 0;
|
|
13995
14160
|
}
|
|
14161
|
+
findAssistant(channelId) {
|
|
14162
|
+
for (const record of this.records.values()) {
|
|
14163
|
+
if (record.isAssistant === true && record.channelId === channelId) {
|
|
14164
|
+
return record;
|
|
14165
|
+
}
|
|
14166
|
+
}
|
|
14167
|
+
return void 0;
|
|
14168
|
+
}
|
|
13996
14169
|
list(channelId) {
|
|
13997
14170
|
const all = [...this.records.values()];
|
|
13998
14171
|
if (channelId) return all.filter((r) => r.channelId === channelId);
|
|
@@ -14072,6 +14245,8 @@ var JsonFileSessionStore = class {
|
|
|
14072
14245
|
for (const [id, record] of this.records) {
|
|
14073
14246
|
if (record.status === "active" || record.status === "initializing")
|
|
14074
14247
|
continue;
|
|
14248
|
+
if (record.isAssistant === true)
|
|
14249
|
+
continue;
|
|
14075
14250
|
const raw = record.lastActiveAt;
|
|
14076
14251
|
if (!raw) continue;
|
|
14077
14252
|
const lastActive = new Date(raw).getTime();
|
|
@@ -14100,6 +14275,7 @@ init_agent_registry();
|
|
|
14100
14275
|
// src/core/agent-switch-handler.ts
|
|
14101
14276
|
init_agent_registry();
|
|
14102
14277
|
init_log();
|
|
14278
|
+
init_events();
|
|
14103
14279
|
var log9 = createChildLogger({ module: "agent-switch" });
|
|
14104
14280
|
var AgentSwitchHandler = class {
|
|
14105
14281
|
constructor(deps) {
|
|
@@ -14125,7 +14301,7 @@ var AgentSwitchHandler = class {
|
|
|
14125
14301
|
if (!agentDef) throw new Error(`Agent "${toAgent}" is not installed`);
|
|
14126
14302
|
const fromAgent = session.agentName;
|
|
14127
14303
|
const middlewareChain = this.deps.getMiddlewareChain();
|
|
14128
|
-
const result = await middlewareChain?.execute(
|
|
14304
|
+
const result = await middlewareChain?.execute(Hook.AGENT_BEFORE_SWITCH, {
|
|
14129
14305
|
sessionId,
|
|
14130
14306
|
fromAgent,
|
|
14131
14307
|
toAgent
|
|
@@ -14139,9 +14315,9 @@ var AgentSwitchHandler = class {
|
|
|
14139
14315
|
type: "system_message",
|
|
14140
14316
|
message: `Switching from ${fromAgent} to ${toAgent}...`
|
|
14141
14317
|
};
|
|
14142
|
-
session.emit(
|
|
14143
|
-
eventBus.emit(
|
|
14144
|
-
eventBus.emit(
|
|
14318
|
+
session.emit(SessionEv.AGENT_EVENT, startEvent);
|
|
14319
|
+
eventBus.emit(BusEvent.AGENT_EVENT, { sessionId, event: startEvent });
|
|
14320
|
+
eventBus.emit(BusEvent.SESSION_AGENT_SWITCH, {
|
|
14145
14321
|
sessionId,
|
|
14146
14322
|
fromAgent,
|
|
14147
14323
|
toAgent,
|
|
@@ -14165,11 +14341,12 @@ var AgentSwitchHandler = class {
|
|
|
14165
14341
|
}
|
|
14166
14342
|
const fromAgentSessionId = session.agentSessionId;
|
|
14167
14343
|
const fileService = this.deps.getService("file-service");
|
|
14344
|
+
const configAllowedPaths = configManager.get().workspace?.security?.allowedPaths ?? [];
|
|
14168
14345
|
try {
|
|
14169
14346
|
await session.switchAgent(toAgent, async () => {
|
|
14170
14347
|
if (canResume) {
|
|
14171
14348
|
try {
|
|
14172
|
-
const instance2 = await agentManager.resume(toAgent, session.workingDirectory, lastEntry.agentSessionId);
|
|
14349
|
+
const instance2 = await agentManager.resume(toAgent, session.workingDirectory, lastEntry.agentSessionId, configAllowedPaths);
|
|
14173
14350
|
if (fileService) instance2.addAllowedPath(fileService.baseDir);
|
|
14174
14351
|
resumed = true;
|
|
14175
14352
|
return instance2;
|
|
@@ -14177,7 +14354,7 @@ var AgentSwitchHandler = class {
|
|
|
14177
14354
|
log9.warn({ sessionId, toAgent }, "Resume failed, falling back to new agent with context injection");
|
|
14178
14355
|
}
|
|
14179
14356
|
}
|
|
14180
|
-
const instance = await agentManager.spawn(toAgent, session.workingDirectory);
|
|
14357
|
+
const instance = await agentManager.spawn(toAgent, session.workingDirectory, configAllowedPaths);
|
|
14181
14358
|
if (fileService) instance.addAllowedPath(fileService.baseDir);
|
|
14182
14359
|
try {
|
|
14183
14360
|
const contextService = this.deps.getService("context");
|
|
@@ -14201,9 +14378,9 @@ var AgentSwitchHandler = class {
|
|
|
14201
14378
|
type: "system_message",
|
|
14202
14379
|
message: resumed ? `Switched to ${toAgent} (resumed previous session).` : `Switched to ${toAgent} (new session).`
|
|
14203
14380
|
};
|
|
14204
|
-
session.emit(
|
|
14205
|
-
eventBus.emit(
|
|
14206
|
-
eventBus.emit(
|
|
14381
|
+
session.emit(SessionEv.AGENT_EVENT, successEvent);
|
|
14382
|
+
eventBus.emit(BusEvent.AGENT_EVENT, { sessionId, event: successEvent });
|
|
14383
|
+
eventBus.emit(BusEvent.SESSION_AGENT_SWITCH, {
|
|
14207
14384
|
sessionId,
|
|
14208
14385
|
fromAgent,
|
|
14209
14386
|
toAgent,
|
|
@@ -14216,9 +14393,9 @@ var AgentSwitchHandler = class {
|
|
|
14216
14393
|
type: "system_message",
|
|
14217
14394
|
message: `Failed to switch to ${toAgent}: ${errorMessage}`
|
|
14218
14395
|
};
|
|
14219
|
-
session.emit(
|
|
14220
|
-
eventBus.emit(
|
|
14221
|
-
eventBus.emit(
|
|
14396
|
+
session.emit(SessionEv.AGENT_EVENT, failedEvent);
|
|
14397
|
+
eventBus.emit(BusEvent.AGENT_EVENT, { sessionId, event: failedEvent });
|
|
14398
|
+
eventBus.emit(BusEvent.SESSION_AGENT_SWITCH, {
|
|
14222
14399
|
sessionId,
|
|
14223
14400
|
fromAgent,
|
|
14224
14401
|
toAgent,
|
|
@@ -14267,7 +14444,7 @@ var AgentSwitchHandler = class {
|
|
|
14267
14444
|
currentPromptCount: 0,
|
|
14268
14445
|
agentSwitchHistory: session.agentSwitchHistory
|
|
14269
14446
|
});
|
|
14270
|
-
middlewareChain?.execute(
|
|
14447
|
+
middlewareChain?.execute(Hook.AGENT_AFTER_SWITCH, {
|
|
14271
14448
|
sessionId,
|
|
14272
14449
|
fromAgent,
|
|
14273
14450
|
toAgent,
|
|
@@ -15109,6 +15286,7 @@ function createPluginContext(opts) {
|
|
|
15109
15286
|
}
|
|
15110
15287
|
|
|
15111
15288
|
// src/core/plugin/lifecycle-manager.ts
|
|
15289
|
+
init_events();
|
|
15112
15290
|
var SETUP_TIMEOUT_MS = 3e4;
|
|
15113
15291
|
var TEARDOWN_TIMEOUT_MS = 1e4;
|
|
15114
15292
|
function withTimeout(promise, ms, label) {
|
|
@@ -15254,7 +15432,7 @@ var LifecycleManager = class {
|
|
|
15254
15432
|
}
|
|
15255
15433
|
const registryEntry = this.pluginRegistry?.get(plugin.name);
|
|
15256
15434
|
if (registryEntry && registryEntry.enabled === false) {
|
|
15257
|
-
this.eventBus?.emit(
|
|
15435
|
+
this.eventBus?.emit(BusEvent.PLUGIN_DISABLED, { name: plugin.name });
|
|
15258
15436
|
continue;
|
|
15259
15437
|
}
|
|
15260
15438
|
if (registryEntry && plugin.migrate && registryEntry.version !== plugin.version && this.settingsManager) {
|
|
@@ -15297,7 +15475,7 @@ var LifecycleManager = class {
|
|
|
15297
15475
|
if (!validation.valid) {
|
|
15298
15476
|
this._failed.add(plugin.name);
|
|
15299
15477
|
this.getPluginLogger(plugin.name).error(`Settings validation failed: ${validation.errors?.join("; ")}`);
|
|
15300
|
-
this.eventBus?.emit(
|
|
15478
|
+
this.eventBus?.emit(BusEvent.PLUGIN_FAILED, { name: plugin.name, error: `Settings validation failed: ${validation.errors?.join("; ")}` });
|
|
15301
15479
|
continue;
|
|
15302
15480
|
}
|
|
15303
15481
|
}
|
|
@@ -15320,13 +15498,13 @@ var LifecycleManager = class {
|
|
|
15320
15498
|
await withTimeout(plugin.setup(ctx), SETUP_TIMEOUT_MS, `${plugin.name}.setup()`);
|
|
15321
15499
|
this.contexts.set(plugin.name, ctx);
|
|
15322
15500
|
this._loaded.add(plugin.name);
|
|
15323
|
-
this.eventBus?.emit(
|
|
15501
|
+
this.eventBus?.emit(BusEvent.PLUGIN_LOADED, { name: plugin.name, version: plugin.version });
|
|
15324
15502
|
} catch (err) {
|
|
15325
15503
|
this._failed.add(plugin.name);
|
|
15326
15504
|
ctx.cleanup();
|
|
15327
15505
|
console.error(`[lifecycle] Plugin ${plugin.name} setup() FAILED:`, err);
|
|
15328
15506
|
this.getPluginLogger(plugin.name).error(`setup() failed: ${err}`);
|
|
15329
|
-
this.eventBus?.emit(
|
|
15507
|
+
this.eventBus?.emit(BusEvent.PLUGIN_FAILED, { name: plugin.name, error: String(err) });
|
|
15330
15508
|
}
|
|
15331
15509
|
}
|
|
15332
15510
|
}
|
|
@@ -15347,7 +15525,7 @@ var LifecycleManager = class {
|
|
|
15347
15525
|
this._loaded.delete(name);
|
|
15348
15526
|
this._failed.delete(name);
|
|
15349
15527
|
this.loadOrder = this.loadOrder.filter((p) => p.name !== name);
|
|
15350
|
-
this.eventBus?.emit(
|
|
15528
|
+
this.eventBus?.emit(BusEvent.PLUGIN_UNLOADED, { name });
|
|
15351
15529
|
}
|
|
15352
15530
|
async shutdown() {
|
|
15353
15531
|
const reversed = [...this.loadOrder].reverse();
|
|
@@ -15364,7 +15542,7 @@ var LifecycleManager = class {
|
|
|
15364
15542
|
ctx.cleanup();
|
|
15365
15543
|
this.contexts.delete(plugin.name);
|
|
15366
15544
|
}
|
|
15367
|
-
this.eventBus?.emit(
|
|
15545
|
+
this.eventBus?.emit(BusEvent.PLUGIN_UNLOADED, { name: plugin.name });
|
|
15368
15546
|
}
|
|
15369
15547
|
this._loaded.clear();
|
|
15370
15548
|
this.loadOrder = [];
|
|
@@ -15512,21 +15690,25 @@ var AssistantManager = class {
|
|
|
15512
15690
|
this.registry = registry;
|
|
15513
15691
|
}
|
|
15514
15692
|
sessions = /* @__PURE__ */ new Map();
|
|
15515
|
-
respawning = /* @__PURE__ */ new Set();
|
|
15516
15693
|
pendingSystemPrompts = /* @__PURE__ */ new Map();
|
|
15517
|
-
async
|
|
15694
|
+
async getOrSpawn(channelId, threadId) {
|
|
15695
|
+
const existing = this.core.sessionStore?.findAssistant(channelId);
|
|
15518
15696
|
const session = await this.core.createSession({
|
|
15519
15697
|
channelId,
|
|
15520
15698
|
agentName: this.core.configManager.get().defaultAgent,
|
|
15521
15699
|
workingDirectory: this.core.configManager.resolveWorkspace(),
|
|
15522
15700
|
initialName: "Assistant",
|
|
15523
15701
|
isAssistant: true,
|
|
15524
|
-
threadId
|
|
15702
|
+
threadId,
|
|
15703
|
+
existingSessionId: existing?.sessionId
|
|
15525
15704
|
});
|
|
15526
15705
|
this.sessions.set(channelId, session);
|
|
15527
15706
|
const systemPrompt = this.registry.buildSystemPrompt(channelId);
|
|
15528
15707
|
this.pendingSystemPrompts.set(channelId, systemPrompt);
|
|
15529
|
-
log15.info(
|
|
15708
|
+
log15.info(
|
|
15709
|
+
{ sessionId: session.id, channelId, reused: !!existing },
|
|
15710
|
+
existing ? "Assistant session reused (system prompt deferred)" : "Assistant spawned (system prompt deferred)"
|
|
15711
|
+
);
|
|
15530
15712
|
return session;
|
|
15531
15713
|
}
|
|
15532
15714
|
get(channelId) {
|
|
@@ -15547,19 +15729,6 @@ var AssistantManager = class {
|
|
|
15547
15729
|
}
|
|
15548
15730
|
return false;
|
|
15549
15731
|
}
|
|
15550
|
-
async respawn(channelId, threadId) {
|
|
15551
|
-
if (this.respawning.has(channelId)) {
|
|
15552
|
-
return this.sessions.get(channelId);
|
|
15553
|
-
}
|
|
15554
|
-
this.respawning.add(channelId);
|
|
15555
|
-
try {
|
|
15556
|
-
const old = this.sessions.get(channelId);
|
|
15557
|
-
if (old) await old.destroy();
|
|
15558
|
-
return await this.spawn(channelId, threadId);
|
|
15559
|
-
} finally {
|
|
15560
|
-
this.respawning.delete(channelId);
|
|
15561
|
-
}
|
|
15562
|
-
}
|
|
15563
15732
|
};
|
|
15564
15733
|
|
|
15565
15734
|
// src/core/assistant/sections/sessions.ts
|
|
@@ -15729,6 +15898,7 @@ function registerCoreMenuItems(registry) {
|
|
|
15729
15898
|
|
|
15730
15899
|
// src/core/core.ts
|
|
15731
15900
|
init_log();
|
|
15901
|
+
init_events();
|
|
15732
15902
|
var log16 = createChildLogger({ module: "core" });
|
|
15733
15903
|
var OpenACPCore = class {
|
|
15734
15904
|
configManager;
|
|
@@ -15963,7 +16133,7 @@ var OpenACPCore = class {
|
|
|
15963
16133
|
);
|
|
15964
16134
|
if (this.lifecycleManager?.middlewareChain) {
|
|
15965
16135
|
const result = await this.lifecycleManager.middlewareChain.execute(
|
|
15966
|
-
|
|
16136
|
+
Hook.MESSAGE_INCOMING,
|
|
15967
16137
|
message,
|
|
15968
16138
|
async (msg) => msg
|
|
15969
16139
|
);
|
|
@@ -16015,9 +16185,10 @@ ${text3}`;
|
|
|
16015
16185
|
}
|
|
16016
16186
|
}
|
|
16017
16187
|
const sourceAdapterId = message.routing?.sourceAdapterId ?? message.channelId;
|
|
16188
|
+
const routing = sourceAdapterId !== message.routing?.sourceAdapterId ? { ...message.routing, sourceAdapterId } : message.routing;
|
|
16018
16189
|
if (sourceAdapterId && sourceAdapterId !== "sse" && sourceAdapterId !== "api") {
|
|
16019
16190
|
const turnId = nanoid3(8);
|
|
16020
|
-
this.eventBus.emit(
|
|
16191
|
+
this.eventBus.emit(BusEvent.MESSAGE_QUEUED, {
|
|
16021
16192
|
sessionId: session.id,
|
|
16022
16193
|
turnId,
|
|
16023
16194
|
text: text3,
|
|
@@ -16026,9 +16197,9 @@ ${text3}`;
|
|
|
16026
16197
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16027
16198
|
queueDepth: session.queueDepth
|
|
16028
16199
|
});
|
|
16029
|
-
await session.enqueuePrompt(text3, message.attachments,
|
|
16200
|
+
await session.enqueuePrompt(text3, message.attachments, routing, turnId);
|
|
16030
16201
|
} else {
|
|
16031
|
-
await session.enqueuePrompt(text3, message.attachments,
|
|
16202
|
+
await session.enqueuePrompt(text3, message.attachments, routing);
|
|
16032
16203
|
}
|
|
16033
16204
|
}
|
|
16034
16205
|
// --- Unified Session Creation Pipeline ---
|
|
@@ -16072,6 +16243,7 @@ ${text3}`;
|
|
|
16072
16243
|
createdAt: session.createdAt.toISOString(),
|
|
16073
16244
|
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16074
16245
|
name: session.name,
|
|
16246
|
+
isAssistant: params.isAssistant,
|
|
16075
16247
|
platform: platform2,
|
|
16076
16248
|
platforms,
|
|
16077
16249
|
firstAgent: session.firstAgent,
|
|
@@ -16087,7 +16259,7 @@ ${text3}`;
|
|
|
16087
16259
|
log16.warn({ err, sessionId: session.id }, "Failed to flush pending skill commands");
|
|
16088
16260
|
});
|
|
16089
16261
|
if (params.createThread && session.threadId) {
|
|
16090
|
-
this.eventBus.emit(
|
|
16262
|
+
this.eventBus.emit(BusEvent.SESSION_THREAD_READY, {
|
|
16091
16263
|
sessionId: session.id,
|
|
16092
16264
|
channelId: params.channelId,
|
|
16093
16265
|
threadId: session.threadId
|
|
@@ -16111,6 +16283,36 @@ ${text3}`;
|
|
|
16111
16283
|
);
|
|
16112
16284
|
return allowOption.id;
|
|
16113
16285
|
};
|
|
16286
|
+
session.on(SessionEv.NAMED, async (name) => {
|
|
16287
|
+
await this.sessionManager.patchRecord(session.id, { name });
|
|
16288
|
+
this.eventBus.emit(BusEvent.SESSION_UPDATED, { sessionId: session.id, name });
|
|
16289
|
+
});
|
|
16290
|
+
const mw = () => this.lifecycleManager?.middlewareChain;
|
|
16291
|
+
session.on(SessionEv.AGENT_EVENT, async (event) => {
|
|
16292
|
+
let processedEvent = event;
|
|
16293
|
+
const chain = mw();
|
|
16294
|
+
if (chain) {
|
|
16295
|
+
const result = await chain.execute(Hook.AGENT_BEFORE_EVENT, { sessionId: session.id, event }, async (e) => e);
|
|
16296
|
+
if (!result) return;
|
|
16297
|
+
processedEvent = result.event;
|
|
16298
|
+
}
|
|
16299
|
+
if (processedEvent.type === "session_end") {
|
|
16300
|
+
session.finish(processedEvent.reason);
|
|
16301
|
+
} else if (processedEvent.type === "error") {
|
|
16302
|
+
session.fail(processedEvent.message);
|
|
16303
|
+
}
|
|
16304
|
+
this.eventBus.emit(BusEvent.AGENT_EVENT, { sessionId: session.id, event: processedEvent });
|
|
16305
|
+
});
|
|
16306
|
+
session.on(SessionEv.STATUS_CHANGE, (_from, to) => {
|
|
16307
|
+
this.sessionManager.patchRecord(session.id, {
|
|
16308
|
+
status: to,
|
|
16309
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16310
|
+
});
|
|
16311
|
+
this.eventBus.emit(BusEvent.SESSION_UPDATED, { sessionId: session.id, status: to });
|
|
16312
|
+
});
|
|
16313
|
+
session.on(SessionEv.PROMPT_COUNT_CHANGED, (count) => {
|
|
16314
|
+
this.sessionManager.patchRecord(session.id, { currentPromptCount: count });
|
|
16315
|
+
});
|
|
16114
16316
|
}
|
|
16115
16317
|
this.sessionFactory.wireSideEffects(session, {
|
|
16116
16318
|
eventBus: this.eventBus,
|