@alpacakit/agents 0.1.0-beta.37 → 0.2.0-beta.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/README.md +121 -1
- package/dist/agents/claude-session.d.ts +37 -0
- package/dist/agents/claude-session.d.ts.map +1 -0
- package/dist/agents/claude-session.js +419 -0
- package/dist/agents/claude-stream.d.ts +65 -0
- package/dist/agents/claude-stream.d.ts.map +1 -0
- package/dist/agents/claude-stream.js +111 -0
- package/dist/agents/claude.d.ts +2 -1
- package/dist/agents/claude.d.ts.map +1 -1
- package/dist/agents/claude.js +6 -107
- package/dist/agents/codex-session.d.ts +52 -0
- package/dist/agents/codex-session.d.ts.map +1 -0
- package/dist/agents/codex-session.js +495 -0
- package/dist/agents/codex.d.ts +2 -1
- package/dist/agents/codex.d.ts.map +1 -1
- package/dist/agents/codex.js +3 -1
- package/dist/codex/app-server.d.ts +50 -1
- package/dist/codex/app-server.d.ts.map +1 -1
- package/dist/codex/app-server.js +130 -27
- package/dist/codex/index.d.ts +3 -2
- package/dist/codex/index.d.ts.map +1 -1
- package/dist/codex/index.js +2 -1
- package/dist/codex/protocol.d.ts +101 -0
- package/dist/codex/protocol.d.ts.map +1 -1
- package/dist/codex/protocol.js +78 -0
- package/dist/core/process-close.d.ts +11 -0
- package/dist/core/process-close.d.ts.map +1 -0
- package/dist/core/process-close.js +27 -0
- package/dist/core/session.d.ts +100 -0
- package/dist/core/session.d.ts.map +1 -0
- package/dist/core/session.js +29 -0
- package/dist/host/session-files.d.ts +22 -0
- package/dist/host/session-files.d.ts.map +1 -0
- package/dist/host/session-files.js +91 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/internal/controlled-process/host-controller.d.ts.map +1 -1
- package/dist/internal/controlled-process/host-controller.js +23 -1
- package/dist/internal/controlled-process/supervisor-runtime.js +41 -2
- package/dist/kernel/definition.d.ts +11 -2
- package/dist/kernel/definition.d.ts.map +1 -1
- package/dist/kernel/facets.d.ts +38 -1
- package/dist/kernel/facets.d.ts.map +1 -1
- package/dist/kernel/facets.js +20 -0
- package/dist/kernel/runtime.d.ts +10 -6
- package/dist/kernel/runtime.d.ts.map +1 -1
- package/dist/kernel/runtime.js +4 -1
- package/dist/kernel/session.d.ts +97 -0
- package/dist/kernel/session.d.ts.map +1 -0
- package/dist/kernel/session.js +255 -0
- package/dist/testing/fake-exec.d.ts +6 -0
- package/dist/testing/fake-exec.d.ts.map +1 -1
- package/dist/testing/fake-exec.js +3 -1
- package/package.json +2 -2
- package/dist/auth/profile-store.d.ts +0 -14
- package/dist/auth/profile-store.d.ts.map +0 -1
- package/dist/auth/profile-store.js +0 -102
- package/dist/keyring/index.d.ts +0 -2
- package/dist/keyring/index.d.ts.map +0 -1
- package/dist/keyring/index.js +0 -1
- package/dist/keyring/resolver.d.ts +0 -9
- package/dist/keyring/resolver.d.ts.map +0 -1
- package/dist/keyring/resolver.js +0 -64
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session driver pieces shared by the built-in hosts: the definition-side
|
|
3
|
+
* capability contract, the long-lived interactive child (spawn, NDJSON
|
|
4
|
+
* lines, EOF-first close), and the turn/event state
|
|
5
|
+
* every Session keeps. Protocol knowledge (what to write, how to read a
|
|
6
|
+
* line) stays in the adapter modules.
|
|
7
|
+
*/
|
|
8
|
+
import { AGENT_ERROR_CODE, AgentError } from "../core/error.js";
|
|
9
|
+
import type { Clock, Exec, ExecExit, Logger } from "../core/ports.js";
|
|
10
|
+
import { type Session, type SessionEvent, type SessionFacts, type TurnOutcome, type TurnResult } from "../core/session.js";
|
|
11
|
+
import type { HostContext } from "../host/context.js";
|
|
12
|
+
/** Resolved (rule 2): what a session adapter gets to open or resume. */
|
|
13
|
+
export type SessionContext = {
|
|
14
|
+
/** Fully layered: baseEnv → options.env. */
|
|
15
|
+
env: Record<string, string>;
|
|
16
|
+
exec: Exec;
|
|
17
|
+
clock: Clock;
|
|
18
|
+
/** Pre-scoped to the session by the kernel. */
|
|
19
|
+
logger: Logger;
|
|
20
|
+
};
|
|
21
|
+
export type ResolvedSessionOpenOptions<Permissions> = {
|
|
22
|
+
workspace: string;
|
|
23
|
+
permissions: Permissions;
|
|
24
|
+
};
|
|
25
|
+
export type ResolvedSessionResumeOptions<Permissions> = ResolvedSessionOpenOptions<Permissions> & {
|
|
26
|
+
/** Codex only: unarchive an archived thread before resuming it. */
|
|
27
|
+
unarchive: boolean;
|
|
28
|
+
};
|
|
29
|
+
/** Definition-side session capability; absent = `Agent.session` is `null`. */
|
|
30
|
+
export type SessionCapability<Permissions> = {
|
|
31
|
+
open(options: ResolvedSessionOpenOptions<Permissions>, ctx: SessionContext): Promise<Session>;
|
|
32
|
+
resume(id: string, options: ResolvedSessionResumeOptions<Permissions>, ctx: SessionContext): Promise<Session>;
|
|
33
|
+
inspect(id: string, ctx: HostContext): Promise<SessionFacts<Permissions> | null>;
|
|
34
|
+
};
|
|
35
|
+
export declare function assertHostSessionId(id: string, agentId: string): void;
|
|
36
|
+
export declare function sessionStateError(agentId: string, reason: string): AgentError<typeof AGENT_ERROR_CODE.invalidInput>;
|
|
37
|
+
export declare function sessionExitedError(agentId: string, exit: ExecExit | null, stderrTail: string): AgentError<typeof AGENT_ERROR_CODE.failed>;
|
|
38
|
+
/**
|
|
39
|
+
* One interactive child held for the life of a Session: stdout is read as
|
|
40
|
+
* NDJSON lines, stdin stays open for incremental writes, and `close()`
|
|
41
|
+
* ends stdin first (both hosts exit on EOF) before escalating to signals.
|
|
42
|
+
*/
|
|
43
|
+
export declare class SessionProcess {
|
|
44
|
+
private readonly process;
|
|
45
|
+
private readonly clock;
|
|
46
|
+
private exitValue;
|
|
47
|
+
private stderrTail;
|
|
48
|
+
private constructor();
|
|
49
|
+
static spawn(input: {
|
|
50
|
+
command: string;
|
|
51
|
+
args: readonly string[];
|
|
52
|
+
cwd: string;
|
|
53
|
+
ctx: SessionContext;
|
|
54
|
+
agentId: string;
|
|
55
|
+
}): Promise<SessionProcess>;
|
|
56
|
+
get exit(): Promise<ExecExit>;
|
|
57
|
+
get exited(): ExecExit | null;
|
|
58
|
+
get stderr(): string;
|
|
59
|
+
writeLine(line: string): void;
|
|
60
|
+
/** Non-empty stdout lines in order; stderr is kept as a tail for errors. */
|
|
61
|
+
lines(): AsyncGenerator<string, void, void>;
|
|
62
|
+
close(): Promise<ExecExit>;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Turn bookkeeping and the event stream. Exactly one turn runs at a time:
|
|
66
|
+
* `send` needs idle, `steer`/`interrupt` need active — both are typed
|
|
67
|
+
* `invalid_input`, never a silent queue.
|
|
68
|
+
*/
|
|
69
|
+
export declare class SessionState {
|
|
70
|
+
private readonly agentId;
|
|
71
|
+
private readonly broadcast;
|
|
72
|
+
private active;
|
|
73
|
+
/** A turn whose start the host has not acknowledged yet (Codex `turn/start` in flight). */
|
|
74
|
+
private starting;
|
|
75
|
+
private ended;
|
|
76
|
+
constructor(agentId: string);
|
|
77
|
+
get events(): AsyncIterable<SessionEvent>;
|
|
78
|
+
get activeTurnId(): string | null;
|
|
79
|
+
get isEnded(): boolean;
|
|
80
|
+
get isStarting(): boolean;
|
|
81
|
+
emit(event: SessionEvent): void;
|
|
82
|
+
/** Record a message for the turn's fallback text and publish it. */
|
|
83
|
+
message(text: string): void;
|
|
84
|
+
requireOpen(): void;
|
|
85
|
+
requireIdle(): void;
|
|
86
|
+
/** Reserve the idle slot while the host is asked to start the turn. */
|
|
87
|
+
markStarting(): void;
|
|
88
|
+
/** The host refused to start the turn; the slot is idle again. */
|
|
89
|
+
clearStarting(): void;
|
|
90
|
+
requireActive(): string;
|
|
91
|
+
beginTurn(turnId: string): Promise<TurnResult>;
|
|
92
|
+
/** `text === null` falls back to the turn's collected messages. */
|
|
93
|
+
endTurn(outcome: TurnOutcome, text: string | null): void;
|
|
94
|
+
/** The process is gone: fail the running turn, publish `exited`, end the stream. */
|
|
95
|
+
end(exit: ExecExit, stderrTail: string): void;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/kernel/session.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,gBAAgB,EAChB,UAAU,EAEX,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EACV,KAAK,EACL,IAAI,EACJ,QAAQ,EAER,MAAM,EACP,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAEL,KAAK,OAAO,EACZ,KAAK,YAAY,EACjB,KAAK,YAAY,EAEjB,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGtD,wEAAwE;AACxE,MAAM,MAAM,cAAc,GAAG;IAC3B,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,IAAI,EAAE,IAAI,CAAC;IACX,KAAK,EAAE,KAAK,CAAC;IACb,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,WAAW,IAAI;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,WAAW,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,4BAA4B,CAAC,WAAW,IAClD,0BAA0B,CAAC,WAAW,CAAC,GAAG;IACxC,mEAAmE;IACnE,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC;AAEJ,8EAA8E;AAC9E,MAAM,MAAM,iBAAiB,CAAC,WAAW,IAAI;IAC3C,IAAI,CACF,OAAO,EAAE,0BAA0B,CAAC,WAAW,CAAC,EAChD,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,OAAO,CAAC,CAAC;IACpB,MAAM,CACJ,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,4BAA4B,CAAC,WAAW,CAAC,EAClD,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,OAAO,CAAC,CAAC;IACpB,OAAO,CACL,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,YAAY,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC,CAAC;CAC9C,CAAC;AAQF,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAWrE;AAgBD,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GACb,UAAU,CAAC,OAAO,gBAAgB,CAAC,YAAY,CAAC,CAQlD;AAED,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,QAAQ,GAAG,IAAI,EACrB,UAAU,EAAE,MAAM,GACjB,UAAU,CAAC,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAa5C;AAED;;;;GAIG;AACH,qBAAa,cAAc;IAKvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK;IALxB,OAAO,CAAC,SAAS,CAAyB;IAC1C,OAAO,CAAC,UAAU,CAAM;IAExB,OAAO;WAcM,KAAK,CAAC,KAAK,EAAE;QACxB,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;QACxB,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,cAAc,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,cAAc,CAAC;IAsB3B,IAAI,IAAI,IAAI,OAAO,CAAC,QAAQ,CAAC,CAE5B;IAED,IAAI,MAAM,IAAI,QAAQ,GAAG,IAAI,CAE5B;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI7B,4EAA4E;IACrE,KAAK,IAAI,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC;IAuBlD,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC;CAM3B;AASD;;;;GAIG;AACH,qBAAa,YAAY;IAUX,OAAO,CAAC,QAAQ,CAAC,OAAO;IATpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAGxB;IACF,OAAO,CAAC,MAAM,CAA2B;IACzC,2FAA2F;IAC3F,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,KAAK,CAAS;gBAEO,OAAO,EAAE,MAAM;IAE5C,IAAI,MAAM,IAAI,aAAa,CAAC,YAAY,CAAC,CAExC;IAED,IAAI,YAAY,IAAI,MAAM,GAAG,IAAI,CAEhC;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,UAAU,IAAI,OAAO,CAExB;IAED,IAAI,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAI/B,oEAAoE;IACpE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAK3B,WAAW,IAAI,IAAI;IAMnB,WAAW,IAAI,IAAI;IAUnB,uEAAuE;IACvE,YAAY,IAAI,IAAI;IAIpB,kEAAkE;IAClE,aAAa,IAAI,IAAI;IAIrB,aAAa,IAAI,MAAM;IAQvB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAQ9C,mEAAmE;IACnE,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAkBxD,oFAAoF;IACpF,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;CAuB9C"}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session driver pieces shared by the built-in hosts: the definition-side
|
|
3
|
+
* capability contract, the long-lived interactive child (spawn, NDJSON
|
|
4
|
+
* lines, EOF-first close), and the turn/event state
|
|
5
|
+
* every Session keeps. Protocol knowledge (what to write, how to read a
|
|
6
|
+
* line) stays in the adapter modules.
|
|
7
|
+
*/
|
|
8
|
+
import { AGENT_ERROR_CODE, AgentError, NOT_AVAILABLE_CAUSE, } from "../core/error.js";
|
|
9
|
+
import { redactSecrets } from "../core/redact.js";
|
|
10
|
+
import { SESSION_EVENT_TYPE, TURN_OUTCOME, } from "../core/session.js";
|
|
11
|
+
import { endProcessGracefully } from "../core/process-close.js";
|
|
12
|
+
import { OUTPUT_CHANNEL } from "../core/values.js";
|
|
13
|
+
import { EventBroadcast } from "./handle.js";
|
|
14
|
+
const OUTPUT_TAIL_CHARS = 2_000;
|
|
15
|
+
const EVENT_BASE_COST_BYTES = 64;
|
|
16
|
+
/** Both hosts use UUID-shaped conversation ids; anything else never reaches a path. */
|
|
17
|
+
const HOST_SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
|
|
18
|
+
export function assertHostSessionId(id, agentId) {
|
|
19
|
+
if (HOST_SESSION_ID.test(id)) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
throw new AgentError({
|
|
23
|
+
code: AGENT_ERROR_CODE.invalidInput,
|
|
24
|
+
classification: AgentError.agentFailure(AGENT_ERROR_CODE.invalidInput),
|
|
25
|
+
message: `Agent "${agentId}" session id is not a UUID`,
|
|
26
|
+
data: { reason: "session id must be a UUID" },
|
|
27
|
+
agentId,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function sessionEventCost(event) {
|
|
31
|
+
return (EVENT_BASE_COST_BYTES +
|
|
32
|
+
(event.type === SESSION_EVENT_TYPE.message ? event.text.length : 0));
|
|
33
|
+
}
|
|
34
|
+
function appendTail(current, next) {
|
|
35
|
+
const combined = current + next;
|
|
36
|
+
return combined.length <= OUTPUT_TAIL_CHARS
|
|
37
|
+
? combined
|
|
38
|
+
: combined.slice(-OUTPUT_TAIL_CHARS);
|
|
39
|
+
}
|
|
40
|
+
export function sessionStateError(agentId, reason) {
|
|
41
|
+
return new AgentError({
|
|
42
|
+
code: AGENT_ERROR_CODE.invalidInput,
|
|
43
|
+
classification: AgentError.agentFailure(AGENT_ERROR_CODE.invalidInput),
|
|
44
|
+
message: `Agent "${agentId}" session: ${reason}`,
|
|
45
|
+
data: { reason },
|
|
46
|
+
agentId,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export function sessionExitedError(agentId, exit, stderrTail) {
|
|
50
|
+
const tail = redactSecrets(stderrTail);
|
|
51
|
+
return new AgentError({
|
|
52
|
+
code: AGENT_ERROR_CODE.failed,
|
|
53
|
+
classification: AgentError.agentFailure(AGENT_ERROR_CODE.failed),
|
|
54
|
+
message: `Agent "${agentId}" session process exited`,
|
|
55
|
+
data: {
|
|
56
|
+
exitCode: exit?.code ?? null,
|
|
57
|
+
stdoutTail: null,
|
|
58
|
+
stderrTail: tail === "" ? null : tail,
|
|
59
|
+
},
|
|
60
|
+
agentId,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* One interactive child held for the life of a Session: stdout is read as
|
|
65
|
+
* NDJSON lines, stdin stays open for incremental writes, and `close()`
|
|
66
|
+
* ends stdin first (both hosts exit on EOF) before escalating to signals.
|
|
67
|
+
*/
|
|
68
|
+
export class SessionProcess {
|
|
69
|
+
process;
|
|
70
|
+
clock;
|
|
71
|
+
exitValue = null;
|
|
72
|
+
stderrTail = "";
|
|
73
|
+
constructor(process, clock) {
|
|
74
|
+
this.process = process;
|
|
75
|
+
this.clock = clock;
|
|
76
|
+
void process.exit.then((exit) => {
|
|
77
|
+
this.exitValue = exit;
|
|
78
|
+
}, () => {
|
|
79
|
+
this.exitValue = { code: null, signal: null };
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
static async spawn(input) {
|
|
83
|
+
const command = await input.ctx.exec.which(input.command, input.ctx.env);
|
|
84
|
+
if (command === null) {
|
|
85
|
+
throw new AgentError({
|
|
86
|
+
code: AGENT_ERROR_CODE.notAvailable,
|
|
87
|
+
classification: AgentError.agentFailure(AGENT_ERROR_CODE.notAvailable),
|
|
88
|
+
message: `Command "${input.command}" was not found on PATH`,
|
|
89
|
+
data: { cause: NOT_AVAILABLE_CAUSE.notInstalled },
|
|
90
|
+
agentId: input.agentId,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
const process = input.ctx.exec.spawn({
|
|
94
|
+
command,
|
|
95
|
+
args: [...input.args],
|
|
96
|
+
cwd: input.cwd,
|
|
97
|
+
env: input.ctx.env,
|
|
98
|
+
stdin: null,
|
|
99
|
+
keepStdinOpen: true,
|
|
100
|
+
});
|
|
101
|
+
return new SessionProcess(process, input.ctx.clock);
|
|
102
|
+
}
|
|
103
|
+
get exit() {
|
|
104
|
+
return this.process.exit;
|
|
105
|
+
}
|
|
106
|
+
get exited() {
|
|
107
|
+
return this.exitValue;
|
|
108
|
+
}
|
|
109
|
+
get stderr() {
|
|
110
|
+
return this.stderrTail;
|
|
111
|
+
}
|
|
112
|
+
writeLine(line) {
|
|
113
|
+
this.process.writeStdin(`${line}\n`);
|
|
114
|
+
}
|
|
115
|
+
/** Non-empty stdout lines in order; stderr is kept as a tail for errors. */
|
|
116
|
+
async *lines() {
|
|
117
|
+
let pending = "";
|
|
118
|
+
for await (const chunk of this.process.chunks) {
|
|
119
|
+
if (chunk.channel === OUTPUT_CHANNEL.stderr) {
|
|
120
|
+
this.stderrTail = appendTail(this.stderrTail, chunk.text);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
pending += chunk.text;
|
|
124
|
+
const lines = pending.split(/\r?\n/u);
|
|
125
|
+
pending = lines.pop() ?? "";
|
|
126
|
+
for (const line of lines) {
|
|
127
|
+
const trimmed = line.trim();
|
|
128
|
+
if (trimmed !== "") {
|
|
129
|
+
yield trimmed;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const last = pending.trim();
|
|
134
|
+
if (last !== "") {
|
|
135
|
+
yield last;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
close() {
|
|
139
|
+
if (this.exitValue !== null) {
|
|
140
|
+
return Promise.resolve(this.exitValue);
|
|
141
|
+
}
|
|
142
|
+
return endProcessGracefully(this.process, this.clock);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Turn bookkeeping and the event stream. Exactly one turn runs at a time:
|
|
147
|
+
* `send` needs idle, `steer`/`interrupt` need active — both are typed
|
|
148
|
+
* `invalid_input`, never a silent queue.
|
|
149
|
+
*/
|
|
150
|
+
export class SessionState {
|
|
151
|
+
agentId;
|
|
152
|
+
broadcast = new EventBroadcast(undefined, sessionEventCost);
|
|
153
|
+
active = null;
|
|
154
|
+
/** A turn whose start the host has not acknowledged yet (Codex `turn/start` in flight). */
|
|
155
|
+
starting = false;
|
|
156
|
+
ended = false;
|
|
157
|
+
constructor(agentId) {
|
|
158
|
+
this.agentId = agentId;
|
|
159
|
+
}
|
|
160
|
+
get events() {
|
|
161
|
+
return this.broadcast.events;
|
|
162
|
+
}
|
|
163
|
+
get activeTurnId() {
|
|
164
|
+
return this.active?.turnId ?? null;
|
|
165
|
+
}
|
|
166
|
+
get isEnded() {
|
|
167
|
+
return this.ended;
|
|
168
|
+
}
|
|
169
|
+
get isStarting() {
|
|
170
|
+
return this.starting;
|
|
171
|
+
}
|
|
172
|
+
emit(event) {
|
|
173
|
+
this.broadcast.push(event);
|
|
174
|
+
}
|
|
175
|
+
/** Record a message for the turn's fallback text and publish it. */
|
|
176
|
+
message(text) {
|
|
177
|
+
this.active?.messages.push(text);
|
|
178
|
+
this.emit({ type: SESSION_EVENT_TYPE.message, text });
|
|
179
|
+
}
|
|
180
|
+
requireOpen() {
|
|
181
|
+
if (this.ended) {
|
|
182
|
+
throw sessionStateError(this.agentId, "session is closed");
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
requireIdle() {
|
|
186
|
+
this.requireOpen();
|
|
187
|
+
if (this.active !== null || this.starting) {
|
|
188
|
+
throw sessionStateError(this.agentId, "a turn is in progress; use steer() or interrupt()");
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/** Reserve the idle slot while the host is asked to start the turn. */
|
|
192
|
+
markStarting() {
|
|
193
|
+
this.starting = true;
|
|
194
|
+
}
|
|
195
|
+
/** The host refused to start the turn; the slot is idle again. */
|
|
196
|
+
clearStarting() {
|
|
197
|
+
this.starting = false;
|
|
198
|
+
}
|
|
199
|
+
requireActive() {
|
|
200
|
+
this.requireOpen();
|
|
201
|
+
if (this.active === null) {
|
|
202
|
+
throw sessionStateError(this.agentId, "no turn is in progress");
|
|
203
|
+
}
|
|
204
|
+
return this.active.turnId;
|
|
205
|
+
}
|
|
206
|
+
beginTurn(turnId) {
|
|
207
|
+
this.starting = false;
|
|
208
|
+
return new Promise((resolve, reject) => {
|
|
209
|
+
this.active = { turnId, messages: [], resolve, reject };
|
|
210
|
+
this.emit({ type: SESSION_EVENT_TYPE.turnStarted, turnId });
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
/** `text === null` falls back to the turn's collected messages. */
|
|
214
|
+
endTurn(outcome, text) {
|
|
215
|
+
const turn = this.active;
|
|
216
|
+
if (turn === null) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
this.active = null;
|
|
220
|
+
this.emit({
|
|
221
|
+
type: SESSION_EVENT_TYPE.turnEnded,
|
|
222
|
+
turnId: turn.turnId,
|
|
223
|
+
outcome,
|
|
224
|
+
});
|
|
225
|
+
turn.resolve({
|
|
226
|
+
turnId: turn.turnId,
|
|
227
|
+
outcome,
|
|
228
|
+
text: text ?? turn.messages.join("\n"),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
/** The process is gone: fail the running turn, publish `exited`, end the stream. */
|
|
232
|
+
end(exit, stderrTail) {
|
|
233
|
+
if (this.ended) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
this.ended = true;
|
|
237
|
+
this.starting = false;
|
|
238
|
+
const turn = this.active;
|
|
239
|
+
this.active = null;
|
|
240
|
+
if (turn !== null) {
|
|
241
|
+
this.emit({
|
|
242
|
+
type: SESSION_EVENT_TYPE.turnEnded,
|
|
243
|
+
turnId: turn.turnId,
|
|
244
|
+
outcome: TURN_OUTCOME.failed,
|
|
245
|
+
});
|
|
246
|
+
turn.reject(sessionExitedError(this.agentId, exit, stderrTail));
|
|
247
|
+
}
|
|
248
|
+
this.emit({
|
|
249
|
+
type: SESSION_EVENT_TYPE.exited,
|
|
250
|
+
code: exit.code,
|
|
251
|
+
signal: exit.signal,
|
|
252
|
+
});
|
|
253
|
+
this.broadcast.end();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -13,6 +13,12 @@ export type FakeExecScript = {
|
|
|
13
13
|
stderr(text: string): void;
|
|
14
14
|
exit(exit: ExecExit): void;
|
|
15
15
|
}) => void;
|
|
16
|
+
/** Runs when the caller ends stdin — script `controls.exit(...)` here for a child that exits on EOF. */
|
|
17
|
+
onStdinEnd?: (controls: {
|
|
18
|
+
stdout(text: string): void;
|
|
19
|
+
stderr(text: string): void;
|
|
20
|
+
exit(exit: ExecExit): void;
|
|
21
|
+
}) => void;
|
|
16
22
|
};
|
|
17
23
|
export type FakeExec = Exec & {
|
|
18
24
|
readonly spawns: (ExecSpawnOptions | InteractiveExecSpawnOptions)[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fake-exec.d.ts","sourceRoot":"","sources":["../../src/testing/fake-exec.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,IAAI,EACJ,SAAS,EACT,QAAQ,EACR,gBAAgB,EAEhB,2BAA2B,EAC5B,MAAM,kBAAkB,CAAC;AAG1B,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,6DAA6D;IAC7D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,CACR,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE;QACR,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;KAC5B,KACE,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"fake-exec.d.ts","sourceRoot":"","sources":["../../src/testing/fake-exec.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,IAAI,EACJ,SAAS,EACT,QAAQ,EACR,gBAAgB,EAEhB,2BAA2B,EAC5B,MAAM,kBAAkB,CAAC;AAG1B,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,6DAA6D;IAC7D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,CACR,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE;QACR,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;KAC5B,KACE,IAAI,CAAC;IACV,wGAAwG;IACxG,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE;QACtB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;KAC5B,KAAK,IAAI,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG;IAC5B,QAAQ,CAAC,MAAM,EAAE,CAAC,gBAAgB,GAAG,2BAA2B,CAAC,EAAE,CAAC;IACpE,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,CAAC;IACjC,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC;IAC3B,iDAAiD;IACjD,QAAQ,CAAC,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACvC,CAAC;AAWF,wBAAgB,QAAQ,CAAC,OAAO,GAAE,cAAc,EAAO,GAAG,QAAQ,CAqFjE"}
|
|
@@ -56,7 +56,9 @@ export function fakeExec(scripts = []) {
|
|
|
56
56
|
writes.push(text);
|
|
57
57
|
script.onWrite?.(text, controls);
|
|
58
58
|
},
|
|
59
|
-
endStdin: () => {
|
|
59
|
+
endStdin: () => {
|
|
60
|
+
script.onStdinEnd?.(controls);
|
|
61
|
+
},
|
|
60
62
|
kill: (signal) => {
|
|
61
63
|
killSignals.push(signal);
|
|
62
64
|
if (signal === PROCESS_SIGNAL.sigterm &&
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alpacakit/agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-beta.2",
|
|
4
4
|
"description": "Run AI coding agents programmatically: execute, stream, classify failures, fail over.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"zod": "^4.3.6",
|
|
48
|
-
"@alpacakit/core": "0.
|
|
48
|
+
"@alpacakit/core": "0.2.0-beta.2"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"typescript": "^5.9.3",
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal file-backed profile store (A11): rows follow rule 2 and never
|
|
3
|
-
* contain secret values (A9) — materials carry refs and paths only.
|
|
4
|
-
* Profile management UX is consumer territory. Unlike fleet state, this
|
|
5
|
-
* is durable user data: malformed or unknown-version files are typed
|
|
6
|
-
* errors, never silent resets.
|
|
7
|
-
*/
|
|
8
|
-
import { type AuthProfile } from "./material.js";
|
|
9
|
-
export interface ProfileStore {
|
|
10
|
-
list(): Promise<AuthProfile[]>;
|
|
11
|
-
save(profiles: AuthProfile[]): Promise<void>;
|
|
12
|
-
}
|
|
13
|
-
export declare function fileProfileStore(filePath: string): ProfileStore;
|
|
14
|
-
//# sourceMappingURL=profile-store.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"profile-store.d.ts","sourceRoot":"","sources":["../../src/auth/profile-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAcrE,MAAM,WAAW,YAAY;IAC3B,IAAI,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAC/B,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9C;AA+FD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,CAE/D"}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal file-backed profile store (A11): rows follow rule 2 and never
|
|
3
|
-
* contain secret values (A9) — materials carry refs and paths only.
|
|
4
|
-
* Profile management UX is consumer territory. Unlike fleet state, this
|
|
5
|
-
* is durable user data: malformed or unknown-version files are typed
|
|
6
|
-
* errors, never silent resets.
|
|
7
|
-
*/
|
|
8
|
-
import { readFile } from "node:fs/promises";
|
|
9
|
-
import { AGENT_ERROR_CODE, AgentError } from "../core/error.js";
|
|
10
|
-
import { isErrnoCode, writeFileAtomically } from "../core/fs.js";
|
|
11
|
-
import { readUnknownRecord } from "../core/json.js";
|
|
12
|
-
import { TEXT_ENCODING } from "../core/values.js";
|
|
13
|
-
import { AUTH_MATERIAL_KIND } from "./material.js";
|
|
14
|
-
const PROFILE_FILE_VERSION = 1;
|
|
15
|
-
const PROFILE_STORE_ERROR_REASON = {
|
|
16
|
-
unreadable: "unreadable file",
|
|
17
|
-
malformedJson: "malformed JSON",
|
|
18
|
-
rootMustBeObject: "root must be an object",
|
|
19
|
-
unsupportedVersion: "unsupported version",
|
|
20
|
-
invalidProfiles: "profiles must be auth profile records",
|
|
21
|
-
};
|
|
22
|
-
function profileStoreError(reason, cause) {
|
|
23
|
-
return new AgentError({
|
|
24
|
-
code: AGENT_ERROR_CODE.invalidInput,
|
|
25
|
-
classification: AgentError.agentFailure(AGENT_ERROR_CODE.invalidInput),
|
|
26
|
-
message: `Invalid auth profile store: ${reason}`,
|
|
27
|
-
data: { reason },
|
|
28
|
-
...(cause === undefined ? {} : { cause }),
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
function isSecretRef(value) {
|
|
32
|
-
const record = readUnknownRecord(value);
|
|
33
|
-
if (record === null) {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
const { resolver, id } = record;
|
|
37
|
-
return typeof resolver === "string" && typeof id === "string";
|
|
38
|
-
}
|
|
39
|
-
function isAuthProfile(value) {
|
|
40
|
-
const record = readUnknownRecord(value);
|
|
41
|
-
if (record === null) {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
const { id, agentId, material: materialValue } = record;
|
|
45
|
-
const material = readUnknownRecord(materialValue);
|
|
46
|
-
if (material === null) {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
const { kind, path, secret } = material;
|
|
50
|
-
const materialValid = (kind === AUTH_MATERIAL_KIND.homeDir && typeof path === "string") ||
|
|
51
|
-
(kind === AUTH_MATERIAL_KIND.apiKey && isSecretRef(secret));
|
|
52
|
-
return typeof id === "string" && typeof agentId === "string" && materialValid;
|
|
53
|
-
}
|
|
54
|
-
function parseProfileFile(raw) {
|
|
55
|
-
let parsed;
|
|
56
|
-
try {
|
|
57
|
-
parsed = JSON.parse(raw);
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
throw profileStoreError(PROFILE_STORE_ERROR_REASON.malformedJson, error);
|
|
61
|
-
}
|
|
62
|
-
const root = readUnknownRecord(parsed);
|
|
63
|
-
if (root === null) {
|
|
64
|
-
throw profileStoreError(PROFILE_STORE_ERROR_REASON.rootMustBeObject);
|
|
65
|
-
}
|
|
66
|
-
const { version, profiles } = root;
|
|
67
|
-
if (version !== PROFILE_FILE_VERSION) {
|
|
68
|
-
throw profileStoreError(PROFILE_STORE_ERROR_REASON.unsupportedVersion);
|
|
69
|
-
}
|
|
70
|
-
if (!Array.isArray(profiles) || !profiles.every(isAuthProfile)) {
|
|
71
|
-
throw profileStoreError(PROFILE_STORE_ERROR_REASON.invalidProfiles);
|
|
72
|
-
}
|
|
73
|
-
return profiles;
|
|
74
|
-
}
|
|
75
|
-
class FileProfileStore {
|
|
76
|
-
filePath;
|
|
77
|
-
constructor(filePath) {
|
|
78
|
-
this.filePath = filePath;
|
|
79
|
-
}
|
|
80
|
-
async list() {
|
|
81
|
-
let raw;
|
|
82
|
-
try {
|
|
83
|
-
raw = await readFile(this.filePath, TEXT_ENCODING);
|
|
84
|
-
}
|
|
85
|
-
catch (error) {
|
|
86
|
-
// Durable user data: only "not created yet" reads as empty; any
|
|
87
|
-
// other read failure surfaces instead of masking real profiles.
|
|
88
|
-
if (isErrnoCode(error, "ENOENT")) {
|
|
89
|
-
return [];
|
|
90
|
-
}
|
|
91
|
-
throw profileStoreError(PROFILE_STORE_ERROR_REASON.unreadable, error);
|
|
92
|
-
}
|
|
93
|
-
return parseProfileFile(raw);
|
|
94
|
-
}
|
|
95
|
-
async save(profiles) {
|
|
96
|
-
const file = { version: PROFILE_FILE_VERSION, profiles };
|
|
97
|
-
await writeFileAtomically(this.filePath, `${JSON.stringify(file, null, 2)}\n`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
export function fileProfileStore(filePath) {
|
|
101
|
-
return new FileProfileStore(filePath);
|
|
102
|
-
}
|
package/dist/keyring/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/keyring/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,KAAK,sBAAsB,GAC5B,MAAM,eAAe,CAAC"}
|
package/dist/keyring/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { keyringResolver, } from "./resolver.js";
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { SecretResolver } from "../core/ports.js";
|
|
2
|
-
export type KeyringResolverOptions = {
|
|
3
|
-
readonly service: string;
|
|
4
|
-
};
|
|
5
|
-
type KeyringModuleLoader = () => Promise<unknown>;
|
|
6
|
-
export declare function keyringResolver(options: KeyringResolverOptions): SecretResolver;
|
|
7
|
-
export declare function createKeyringResolver(options: KeyringResolverOptions, loader: KeyringModuleLoader): SecretResolver;
|
|
8
|
-
export {};
|
|
9
|
-
//# sourceMappingURL=resolver.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/keyring/resolver.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,KAAK,mBAAmB,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;AAiBlD,wBAAgB,eAAe,CAC7B,OAAO,EAAE,sBAAsB,GAC9B,cAAc,CAEhB;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,sBAAsB,EAC/B,MAAM,EAAE,mBAAmB,GAC1B,cAAc,CA4BhB"}
|
package/dist/keyring/resolver.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
// biome-ignore-all lint/style/useNamingConvention: Keyring peer exports mirror upstream PascalCase names.
|
|
2
|
-
import { AGENT_ERROR_CODE, AgentError, NOT_AVAILABLE_CAUSE, } from "../core/error.js";
|
|
3
|
-
const KEYRING_PEER_PACKAGE = "@napi-rs/keyring";
|
|
4
|
-
const KEYRING_ENTRY_EXPORT = "Entry";
|
|
5
|
-
const EMPTY_SECRET_VALUE = "";
|
|
6
|
-
const defaultKeyringModuleLoader = () => import(KEYRING_PEER_PACKAGE);
|
|
7
|
-
export function keyringResolver(options) {
|
|
8
|
-
return createKeyringResolver(options, defaultKeyringModuleLoader);
|
|
9
|
-
}
|
|
10
|
-
export function createKeyringResolver(options, loader) {
|
|
11
|
-
const service = options.service;
|
|
12
|
-
let modulePromise = null;
|
|
13
|
-
const load = () => {
|
|
14
|
-
modulePromise ??= loadKeyringModule(loader);
|
|
15
|
-
return modulePromise;
|
|
16
|
-
};
|
|
17
|
-
return {
|
|
18
|
-
resolve: async (id) => {
|
|
19
|
-
const module = await load();
|
|
20
|
-
try {
|
|
21
|
-
// Absent entries are the `null` return (`Entry.getPassword():
|
|
22
|
-
// string | null` in @napi-rs/keyring); anything thrown is a real
|
|
23
|
-
// keychain failure, never "missing secret".
|
|
24
|
-
const value = await new module.Entry(service, id).getPassword();
|
|
25
|
-
return value === EMPTY_SECRET_VALUE ? null : value;
|
|
26
|
-
}
|
|
27
|
-
catch (error) {
|
|
28
|
-
throw new AgentError({
|
|
29
|
-
code: AGENT_ERROR_CODE.failed,
|
|
30
|
-
classification: AgentError.agentFailure(AGENT_ERROR_CODE.failed),
|
|
31
|
-
message: `Keyring secret "${id}" could not be read`,
|
|
32
|
-
data: { exitCode: null, stdoutTail: null, stderrTail: null },
|
|
33
|
-
cause: error,
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
async function loadKeyringModule(loader) {
|
|
40
|
-
let loaded;
|
|
41
|
-
try {
|
|
42
|
-
loaded = await loader();
|
|
43
|
-
}
|
|
44
|
-
catch (error) {
|
|
45
|
-
throw keyringNotInstalledError(error);
|
|
46
|
-
}
|
|
47
|
-
if (typeof loaded !== "object" || loaded === null) {
|
|
48
|
-
throw keyringNotInstalledError(null);
|
|
49
|
-
}
|
|
50
|
-
const entry = Reflect.get(loaded, KEYRING_ENTRY_EXPORT);
|
|
51
|
-
if (typeof entry !== "function") {
|
|
52
|
-
throw keyringNotInstalledError(null);
|
|
53
|
-
}
|
|
54
|
-
return { Entry: entry };
|
|
55
|
-
}
|
|
56
|
-
function keyringNotInstalledError(cause) {
|
|
57
|
-
return new AgentError({
|
|
58
|
-
code: AGENT_ERROR_CODE.notAvailable,
|
|
59
|
-
classification: AgentError.agentFailure(AGENT_ERROR_CODE.notAvailable),
|
|
60
|
-
message: "Keyring peer is not installed",
|
|
61
|
-
data: { cause: NOT_AVAILABLE_CAUSE.notInstalled },
|
|
62
|
-
cause,
|
|
63
|
-
});
|
|
64
|
-
}
|