@rzx/ohs 1.2.24 → 1.2.25
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 +4 -2
- package/dist/frontend/index.js +297 -189
- package/dist/frontend/index.js.map +20 -19
- package/dist/index.js +8371 -7323
- package/dist/index.js.map +102 -89
- package/dist/migrations/0009_exotic_owl.sql +29 -0
- package/dist/migrations/0010_tool_results.sql +13 -0
- package/dist/migrations/meta/0009_snapshot.json +4136 -0
- package/dist/migrations/meta/0010_snapshot.json +4048 -0
- package/dist/migrations/meta/_journal.json +15 -1
- package/package.json +10 -10
package/dist/frontend/index.js
CHANGED
|
@@ -338,6 +338,23 @@ var init_validation = __esm(() => {
|
|
|
338
338
|
});
|
|
339
339
|
|
|
340
340
|
// ../../packages/domain-contracts/src/inputs/metadata.ts
|
|
341
|
+
function assertPromptMetadataAllowed(input) {
|
|
342
|
+
for (const namespace of ["metadata", "runMetadata"]) {
|
|
343
|
+
const metadata = input[namespace];
|
|
344
|
+
if (!metadata || typeof metadata !== "object")
|
|
345
|
+
continue;
|
|
346
|
+
if (Object.hasOwn(metadata, "outputSchema")) {
|
|
347
|
+
const field = `${namespace}.outputSchema`;
|
|
348
|
+
throw new DomainValidationError(`${field} is a stored snapshot; use explicit outputSchema`, field);
|
|
349
|
+
}
|
|
350
|
+
for (const name of ["pluginUi", "uiAction", "pluginUiSubmission", "pluginAppAction", "pluginAppSubmission", "pluginAppSource"]) {
|
|
351
|
+
if (Object.hasOwn(metadata, name)) {
|
|
352
|
+
const field = `${namespace}.${name}`;
|
|
353
|
+
throw new DomainValidationError(`${field} is reserved for the host`, field);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
341
358
|
var init_metadata = __esm(() => {
|
|
342
359
|
init_validation_error();
|
|
343
360
|
});
|
|
@@ -650,11 +667,43 @@ function decodeClearedSessionWorktreeBinding(value, expectedSessionId) {
|
|
|
650
667
|
}
|
|
651
668
|
return session;
|
|
652
669
|
}
|
|
670
|
+
function decodeJobTarget(value) {
|
|
671
|
+
const target = object(value, "jobTarget");
|
|
672
|
+
const kind = enumField(target, "kind", "jobTarget", ["terminal", "shell", "agent", "dream", "workflow"]);
|
|
673
|
+
if (!stringField(target, "jobId", "jobTarget").trim())
|
|
674
|
+
throw new ProtocolDataError("jobTarget.jobId is required", "jobTarget.jobId");
|
|
675
|
+
if (kind === "agent" && !stringField(target, "runId", "jobTarget").trim())
|
|
676
|
+
throw new ProtocolDataError("jobTarget.runId is required", "jobTarget.runId");
|
|
677
|
+
return target;
|
|
678
|
+
}
|
|
679
|
+
function decodeJobInputReceipt(value) {
|
|
680
|
+
const receipt = object(value, "jobInputReceipt");
|
|
681
|
+
for (const field of ["sessionId", "inputId", "runId"]) {
|
|
682
|
+
if (!stringField(receipt, field, "jobInputReceipt").trim())
|
|
683
|
+
throw new ProtocolDataError(`jobInputReceipt.${field} is required`, `jobInputReceipt.${field}`);
|
|
684
|
+
}
|
|
685
|
+
return receipt;
|
|
686
|
+
}
|
|
687
|
+
function decodeJobListResult(value) {
|
|
688
|
+
const result = object(value, "jobListResult");
|
|
689
|
+
if (!Array.isArray(result.jobs) || !Array.isArray(result.errors))
|
|
690
|
+
throw new ProtocolDataError("Jobs list requires jobs and errors arrays", "jobListResult");
|
|
691
|
+
result.jobs.forEach(decodeJobSnapshot);
|
|
692
|
+
for (const error of result.errors) {
|
|
693
|
+
const item = object(error, "jobListResult.errors");
|
|
694
|
+
enumField(item, "kind", "jobListResult.errors", ["terminal", "shell", "agent", "dream", "workflow"]);
|
|
695
|
+
stringField(item, "message", "jobListResult.errors");
|
|
696
|
+
}
|
|
697
|
+
return result;
|
|
698
|
+
}
|
|
653
699
|
function decodeJobSnapshot(value) {
|
|
654
700
|
const job = object(value, "job");
|
|
655
701
|
for (const field of ["id", "label", "ownerSession", "cwd"])
|
|
656
702
|
stringField(job, field, "job");
|
|
657
703
|
enumField(job, "kind", "job", ["terminal", "shell", "agent", "dream", "workflow"]);
|
|
704
|
+
const target = decodeJobTarget(job.target);
|
|
705
|
+
if (target.kind !== job.kind || target.jobId !== job.id)
|
|
706
|
+
throw new ProtocolDataError("job target does not match snapshot", "job.target");
|
|
658
707
|
enumField(job, "status", "job", ["running", "stopping", "completed", "killed", "failed"]);
|
|
659
708
|
const capabilities = recordField(job, "capabilities", "job");
|
|
660
709
|
for (const field of ["read", "wait", "send", "cancel"]) {
|
|
@@ -1100,6 +1149,18 @@ var init_plugin_apps2 = __esm(() => {
|
|
|
1100
1149
|
});
|
|
1101
1150
|
|
|
1102
1151
|
// ../../packages/protocol/src/requests/requests.ts
|
|
1152
|
+
function assertPromptMetadataAllowed2(input) {
|
|
1153
|
+
adaptDomainInput(() => assertPromptMetadataAllowed(input));
|
|
1154
|
+
}
|
|
1155
|
+
function adaptDomainInput(read) {
|
|
1156
|
+
try {
|
|
1157
|
+
return read();
|
|
1158
|
+
} catch (error) {
|
|
1159
|
+
if (error instanceof DomainValidationError)
|
|
1160
|
+
throw new ProtocolValidationError(error.message, error.field, error.reason);
|
|
1161
|
+
throw error;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1103
1164
|
var ProtocolValidationError;
|
|
1104
1165
|
var init_requests = __esm(() => {
|
|
1105
1166
|
init_inputs();
|
|
@@ -1359,7 +1420,7 @@ function parseAttachmentTransferCapabilities(value) {
|
|
|
1359
1420
|
uploadModes
|
|
1360
1421
|
};
|
|
1361
1422
|
}
|
|
1362
|
-
var CURRENT_PROTOCOL_VERSION =
|
|
1423
|
+
var CURRENT_PROTOCOL_VERSION = 7, PROTOCOL_VERSION_HEADER = "x-vykor-protocol-version";
|
|
1363
1424
|
var init_capabilities = __esm(() => {
|
|
1364
1425
|
init_attachment();
|
|
1365
1426
|
});
|
|
@@ -1629,9 +1690,13 @@ var init_plugin_ui_history = __esm(() => {
|
|
|
1629
1690
|
init_domain_validation();
|
|
1630
1691
|
});
|
|
1631
1692
|
|
|
1693
|
+
// ../../packages/agent-contracts/src/tools/result-text.ts
|
|
1694
|
+
var init_result_text = () => {};
|
|
1695
|
+
|
|
1632
1696
|
// ../../packages/agent-contracts/src/tools/tool-data.ts
|
|
1633
1697
|
var MCP_CALL_RESULT_MAX_BYTES;
|
|
1634
1698
|
var init_tool_data = __esm(() => {
|
|
1699
|
+
init_result_text();
|
|
1635
1700
|
MCP_CALL_RESULT_MAX_BYTES = 1024 * 1024;
|
|
1636
1701
|
});
|
|
1637
1702
|
|
|
@@ -2690,6 +2755,15 @@ class SessionResource {
|
|
|
2690
2755
|
constructor(transport) {
|
|
2691
2756
|
this.transport = transport;
|
|
2692
2757
|
}
|
|
2758
|
+
async checkOutputSchema(input, signal) {
|
|
2759
|
+
assertPromptMetadataAllowed2(input);
|
|
2760
|
+
if (input.outputSchema === undefined)
|
|
2761
|
+
return;
|
|
2762
|
+
const capabilities = await this.transport.ensureProtocol(signal);
|
|
2763
|
+
if ((capabilities.features.structuredDelivery ?? 0) < 1) {
|
|
2764
|
+
throw new Error("Server does not support structured delivery (outputSchema)");
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2693
2767
|
async search(options) {
|
|
2694
2768
|
const { signal, ...query } = options;
|
|
2695
2769
|
const response = await this.transport.request(this.transport.path("/sessions/search", query), { signal });
|
|
@@ -2769,6 +2843,7 @@ class SessionResource {
|
|
|
2769
2843
|
return response.part;
|
|
2770
2844
|
}
|
|
2771
2845
|
async admitPrompt(sessionId, input, options = {}) {
|
|
2846
|
+
await this.checkOutputSchema(input, options.signal);
|
|
2772
2847
|
return await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/prompts`, {
|
|
2773
2848
|
method: "POST",
|
|
2774
2849
|
body: { ...input, id: input.id ?? createPromptRequestId() },
|
|
@@ -2776,6 +2851,7 @@ class SessionResource {
|
|
|
2776
2851
|
});
|
|
2777
2852
|
}
|
|
2778
2853
|
async editLatestPrompt(sessionId, input, options = {}) {
|
|
2854
|
+
await this.checkOutputSchema(input, options.signal);
|
|
2779
2855
|
return await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/prompts/latest/edit`, {
|
|
2780
2856
|
method: "POST",
|
|
2781
2857
|
body: input,
|
|
@@ -2789,6 +2865,7 @@ class SessionResource {
|
|
|
2789
2865
|
return await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/prompts/${encodeURIComponent(inputId)}/cancel`, { method: "POST", body: input, signal: options.signal });
|
|
2790
2866
|
}
|
|
2791
2867
|
async resumeInterruptedRun(sessionId, runId, input = {}, options = {}) {
|
|
2868
|
+
await this.checkOutputSchema(input, options.signal);
|
|
2792
2869
|
return await this.transport.request(`/sessions/${encodeURIComponent(sessionId)}/runs/${encodeURIComponent(runId)}/resume`, {
|
|
2793
2870
|
method: "POST",
|
|
2794
2871
|
body: { ...input, id: input.id ?? createPromptRequestId() },
|
|
@@ -3030,7 +3107,7 @@ class JobResource {
|
|
|
3030
3107
|
...statuses ? { statuses: statuses.join(",") } : {},
|
|
3031
3108
|
...includeFinished !== undefined ? { includeFinished: String(includeFinished) } : {}
|
|
3032
3109
|
}), { signal });
|
|
3033
|
-
return
|
|
3110
|
+
return decodeJobListResult(response);
|
|
3034
3111
|
}
|
|
3035
3112
|
async createBackgroundShell(input, options = {}) {
|
|
3036
3113
|
return await this.transport.request("/background-shells", {
|
|
@@ -3039,28 +3116,32 @@ class JobResource {
|
|
|
3039
3116
|
signal: options.signal
|
|
3040
3117
|
});
|
|
3041
3118
|
}
|
|
3042
|
-
async read(
|
|
3119
|
+
async read(target, options) {
|
|
3043
3120
|
const { signal, ...query } = options;
|
|
3044
|
-
const response = await this.transport.request(this.transport.path(`/jobs/${encodeURIComponent(jobId)}`, query), { signal });
|
|
3121
|
+
const response = await this.transport.request(this.transport.path(`/jobs/${encodeURIComponent(target.jobId)}`, { ...query, kind: target.kind, ...target.kind === "agent" ? { runId: target.runId } : {} }), { signal });
|
|
3045
3122
|
return decodeJobReadResult(response);
|
|
3046
3123
|
}
|
|
3047
|
-
async wait(
|
|
3048
|
-
const response = await this.transport.request(`/jobs/${encodeURIComponent(jobId)}/wait`, {
|
|
3124
|
+
async wait(target, input, options = {}) {
|
|
3125
|
+
const response = await this.transport.request(`/jobs/${encodeURIComponent(target.jobId)}/wait`, {
|
|
3049
3126
|
method: "POST",
|
|
3050
|
-
body: input,
|
|
3127
|
+
body: { ...input, target },
|
|
3051
3128
|
signal: options.signal
|
|
3052
3129
|
});
|
|
3053
3130
|
return decodeJobWaitResult(response);
|
|
3054
3131
|
}
|
|
3055
|
-
async send(
|
|
3056
|
-
await this.transport.request(`/jobs/${encodeURIComponent(jobId)}/input`, {
|
|
3132
|
+
async send(target, input, options = {}) {
|
|
3133
|
+
const response = await this.transport.request(`/jobs/${encodeURIComponent(target.jobId)}/input`, {
|
|
3057
3134
|
method: "POST",
|
|
3058
|
-
body: input,
|
|
3135
|
+
body: { ...input, target },
|
|
3059
3136
|
signal: options.signal
|
|
3060
3137
|
});
|
|
3138
|
+
if (responseField(response, "sent") !== true)
|
|
3139
|
+
throw new ProtocolDataError("Job input was not accepted", "sent");
|
|
3140
|
+
const receipt = response.receipt;
|
|
3141
|
+
return receipt === undefined ? undefined : decodeJobInputReceipt(receipt);
|
|
3061
3142
|
}
|
|
3062
|
-
async cancel(
|
|
3063
|
-
const response = await this.transport.request(`/jobs/${encodeURIComponent(jobId)}/cancel`, { method: "POST", body: input, signal: options.signal });
|
|
3143
|
+
async cancel(target, input, options = {}) {
|
|
3144
|
+
const response = await this.transport.request(`/jobs/${encodeURIComponent(target.jobId)}/cancel`, { method: "POST", body: { ...input, target }, signal: options.signal });
|
|
3064
3145
|
return decodeJobSnapshot(responseField(response, "snapshot"));
|
|
3065
3146
|
}
|
|
3066
3147
|
}
|
|
@@ -3879,43 +3960,162 @@ var init_session_stream_state = __esm(() => {
|
|
|
3879
3960
|
});
|
|
3880
3961
|
|
|
3881
3962
|
// ../../packages/client/src/state/sync.ts
|
|
3882
|
-
|
|
3963
|
+
async function* runEventSync(client, options, initialState = createInitialClientState(), onLiveEvent) {
|
|
3964
|
+
let state = initialState;
|
|
3965
|
+
let cursor = Math.max(0, options.cursor ?? 0, state.lastSeq);
|
|
3966
|
+
let attempt = 0;
|
|
3967
|
+
const signal = options.signal;
|
|
3968
|
+
const delayMs = options.reconnectDelayMs ?? DEFAULT_RECONNECT_DELAY_MS;
|
|
3969
|
+
const reduce = options.sessionId ? applySessionStreamEvent : options.globalReducer ?? applyEvent;
|
|
3970
|
+
if (signal?.aborted)
|
|
3971
|
+
return;
|
|
3972
|
+
const refreshSession = async (initial) => {
|
|
3973
|
+
const snapshot = await client.sessions.getState(options.sessionId, {
|
|
3974
|
+
signal,
|
|
3975
|
+
partView: options.partView
|
|
3976
|
+
});
|
|
3977
|
+
if (signal?.aborted)
|
|
3978
|
+
return;
|
|
3979
|
+
const next = initial ? applySessionSnapshot(state, snapshot) : applySessionStreamSnapshot(state, snapshot);
|
|
3980
|
+
state = Object.keys(next.eventsBySeq).length ? { ...next, eventsBySeq: {} } : next;
|
|
3981
|
+
cursor = Math.max(cursor, snapshot.cursor, state.lastSeq);
|
|
3982
|
+
};
|
|
3983
|
+
try {
|
|
3984
|
+
if (options.sessionId) {
|
|
3985
|
+
await refreshSession(true);
|
|
3986
|
+
} else {
|
|
3987
|
+
const replay = await client.events.list({
|
|
3988
|
+
cursor: options.cursor,
|
|
3989
|
+
partView: options.partView,
|
|
3990
|
+
signal
|
|
3991
|
+
});
|
|
3992
|
+
if (signal?.aborted)
|
|
3993
|
+
return;
|
|
3994
|
+
for (const event of replay) {
|
|
3995
|
+
if (signal?.aborted)
|
|
3996
|
+
return;
|
|
3997
|
+
state = reduce(state, event);
|
|
3998
|
+
yield { event, state, source: "replay" };
|
|
3999
|
+
}
|
|
4000
|
+
cursor = Math.max(cursor, state.lastSeq);
|
|
4001
|
+
}
|
|
4002
|
+
if (signal?.aborted)
|
|
4003
|
+
return;
|
|
4004
|
+
yield { state, source: "snapshot" };
|
|
4005
|
+
let needsSnapshot = false;
|
|
4006
|
+
while (!signal?.aborted) {
|
|
4007
|
+
try {
|
|
4008
|
+
if (needsSnapshot) {
|
|
4009
|
+
await refreshSession(false);
|
|
4010
|
+
if (signal?.aborted)
|
|
4011
|
+
return;
|
|
4012
|
+
needsSnapshot = false;
|
|
4013
|
+
yield { state, source: "snapshot" };
|
|
4014
|
+
}
|
|
4015
|
+
if (signal?.aborted)
|
|
4016
|
+
return;
|
|
4017
|
+
for await (const event of client.events.stream({
|
|
4018
|
+
cursor,
|
|
4019
|
+
sessionId: options.sessionId,
|
|
4020
|
+
signal,
|
|
4021
|
+
partView: options.partView,
|
|
4022
|
+
transportReconnect: false,
|
|
4023
|
+
idleTimeoutMs: options.idleTimeoutMs
|
|
4024
|
+
})) {
|
|
4025
|
+
if (signal?.aborted)
|
|
4026
|
+
return;
|
|
4027
|
+
onLiveEvent?.();
|
|
4028
|
+
if (signal?.aborted)
|
|
4029
|
+
return;
|
|
4030
|
+
attempt = 0;
|
|
4031
|
+
if (event.seq > cursor + 1 && !options.sessionId) {
|
|
4032
|
+
const gap = await client.events.list({ cursor, partView: options.partView, signal });
|
|
4033
|
+
if (signal?.aborted)
|
|
4034
|
+
return;
|
|
4035
|
+
for (const missed of gap) {
|
|
4036
|
+
if (signal?.aborted)
|
|
4037
|
+
return;
|
|
4038
|
+
const before = state;
|
|
4039
|
+
state = reduce(state, missed);
|
|
4040
|
+
if (state !== before)
|
|
4041
|
+
yield { event: missed, state, source: "replay" };
|
|
4042
|
+
}
|
|
4043
|
+
cursor = Math.max(cursor, state.lastSeq);
|
|
4044
|
+
}
|
|
4045
|
+
if (signal?.aborted)
|
|
4046
|
+
return;
|
|
4047
|
+
const before = state;
|
|
4048
|
+
state = reduce(state, event);
|
|
4049
|
+
cursor = Math.max(cursor, state.lastSeq);
|
|
4050
|
+
if (state !== before)
|
|
4051
|
+
yield { event, state, source: "live" };
|
|
4052
|
+
}
|
|
4053
|
+
} catch (error) {
|
|
4054
|
+
if (error instanceof UnsupportedSessionEventSchemaVersionError)
|
|
4055
|
+
throw error;
|
|
4056
|
+
if (isAbortError(error) || signal?.aborted)
|
|
4057
|
+
return;
|
|
4058
|
+
}
|
|
4059
|
+
if (signal?.aborted)
|
|
4060
|
+
return;
|
|
4061
|
+
needsSnapshot = Boolean(options.sessionId);
|
|
4062
|
+
yield { state, source: "reconnecting" };
|
|
4063
|
+
if (!await waitForReconnect2(delayMs(attempt), signal))
|
|
4064
|
+
return;
|
|
4065
|
+
attempt += 1;
|
|
4066
|
+
}
|
|
4067
|
+
} catch (error) {
|
|
4068
|
+
if (!isAbortError(error) && !signal?.aborted)
|
|
4069
|
+
throw error;
|
|
4070
|
+
}
|
|
4071
|
+
}
|
|
4072
|
+
async function waitForReconnect2(ms, signal) {
|
|
4073
|
+
if (signal?.aborted)
|
|
4074
|
+
return false;
|
|
4075
|
+
if (ms <= 0)
|
|
4076
|
+
return true;
|
|
4077
|
+
return new Promise((resolve) => {
|
|
4078
|
+
const onAbort = () => {
|
|
4079
|
+
clearTimeout(timer);
|
|
4080
|
+
resolve(false);
|
|
4081
|
+
};
|
|
4082
|
+
const timer = setTimeout(() => {
|
|
4083
|
+
signal?.removeEventListener("abort", onAbort);
|
|
4084
|
+
resolve(!signal?.aborted);
|
|
4085
|
+
}, ms);
|
|
4086
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
4087
|
+
});
|
|
4088
|
+
}
|
|
4089
|
+
function isAbortError(error) {
|
|
4090
|
+
return Boolean(error && typeof error === "object" && "name" in error && error.name === "AbortError");
|
|
4091
|
+
}
|
|
4092
|
+
var DEFAULT_RECONNECT_DELAY_MS = (attempt) => Math.min(30000, 250 * 2 ** Math.max(0, attempt));
|
|
4093
|
+
var init_sync = __esm(() => {
|
|
4094
|
+
init_reducer();
|
|
4095
|
+
init_session_stream_state();
|
|
4096
|
+
});
|
|
3883
4097
|
|
|
3884
4098
|
// ../../packages/client/src/state/session-sync-controller.ts
|
|
3885
4099
|
class SessionSyncController {
|
|
3886
|
-
|
|
3887
|
-
sessionId;
|
|
4100
|
+
options;
|
|
3888
4101
|
initialCursor;
|
|
3889
4102
|
generation;
|
|
3890
|
-
externalSignal;
|
|
3891
|
-
reconnectDelayMs;
|
|
3892
|
-
onUpdate;
|
|
3893
|
-
onStatusChange;
|
|
3894
|
-
onError;
|
|
3895
4103
|
abortController = new AbortController;
|
|
3896
4104
|
state;
|
|
3897
4105
|
status = "idle";
|
|
3898
4106
|
running = false;
|
|
3899
4107
|
constructor(options) {
|
|
3900
|
-
this.
|
|
3901
|
-
this.sessionId = options.sessionId;
|
|
4108
|
+
this.options = { ...options };
|
|
3902
4109
|
this.state = options.initialState ?? createInitialClientState();
|
|
3903
4110
|
if ((options.cursor ?? 0) > 0 && !options.initialState) {
|
|
3904
4111
|
throw new Error("A non-zero cursor requires initialState so durable state is not skipped");
|
|
3905
4112
|
}
|
|
3906
4113
|
this.initialCursor = Math.max(0, options.cursor ?? 0, this.state.lastSeq);
|
|
3907
4114
|
this.generation = options.generation ?? 0;
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
this.onError = options.onError;
|
|
3913
|
-
if (this.externalSignal) {
|
|
3914
|
-
if (this.externalSignal.aborted) {
|
|
3915
|
-
this.abortController.abort(this.externalSignal.reason);
|
|
3916
|
-
} else {
|
|
3917
|
-
this.externalSignal.addEventListener("abort", () => this.abortController.abort(this.externalSignal?.reason), { once: true });
|
|
3918
|
-
}
|
|
4115
|
+
if (options.signal?.aborted) {
|
|
4116
|
+
this.abortController.abort(options.signal.reason);
|
|
4117
|
+
} else {
|
|
4118
|
+
options.signal?.addEventListener("abort", () => this.abort(options.signal?.reason), { once: true });
|
|
3919
4119
|
}
|
|
3920
4120
|
}
|
|
3921
4121
|
get currentStatus() {
|
|
@@ -3937,169 +4137,59 @@ class SessionSyncController {
|
|
|
3937
4137
|
if (this.status === status)
|
|
3938
4138
|
return;
|
|
3939
4139
|
this.status = status;
|
|
3940
|
-
this.onStatusChange?.(status, this.generation);
|
|
3941
|
-
}
|
|
3942
|
-
emitUpdate(update) {
|
|
3943
|
-
if (this.abortController.signal.aborted)
|
|
3944
|
-
return;
|
|
3945
|
-
this.state = update.state;
|
|
3946
|
-
this.onUpdate?.(update, this.generation);
|
|
4140
|
+
this.options.onStatusChange?.(status, this.generation);
|
|
3947
4141
|
}
|
|
3948
4142
|
async start() {
|
|
3949
4143
|
if (this.running)
|
|
3950
4144
|
return;
|
|
3951
|
-
|
|
3952
|
-
const signal = this.abortController.signal;
|
|
4145
|
+
const signal = this.signal;
|
|
3953
4146
|
if (signal.aborted) {
|
|
3954
4147
|
this.setStatus("idle");
|
|
3955
4148
|
return;
|
|
3956
4149
|
}
|
|
3957
|
-
|
|
3958
|
-
let attempt = 0;
|
|
3959
|
-
const refreshSession = async (sessionId) => {
|
|
3960
|
-
const snapshot = await this.client.sessions.getState(sessionId, { signal });
|
|
3961
|
-
if (signal.aborted)
|
|
3962
|
-
return;
|
|
3963
|
-
const next = this.status === "connecting" ? applySessionSnapshot(this.state, snapshot) : applySessionStreamSnapshot(this.state, snapshot);
|
|
3964
|
-
this.state = Object.keys(next.eventsBySeq).length ? { ...next, eventsBySeq: {} } : next;
|
|
3965
|
-
cursor = Math.max(cursor, snapshot.cursor, this.state.lastSeq);
|
|
3966
|
-
this.setStatus("connected");
|
|
3967
|
-
this.emitUpdate({ state: this.state, source: "snapshot" });
|
|
3968
|
-
};
|
|
4150
|
+
this.running = true;
|
|
3969
4151
|
try {
|
|
3970
4152
|
this.setStatus("connecting");
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
signal
|
|
3978
|
-
});
|
|
4153
|
+
for await (const update of runEventSync(this.options.client, {
|
|
4154
|
+
sessionId: this.options.sessionId,
|
|
4155
|
+
cursor: this.initialCursor,
|
|
4156
|
+
signal,
|
|
4157
|
+
reconnectDelayMs: this.options.reconnectDelayMs
|
|
4158
|
+
}, this.state, () => this.setStatus("connected"))) {
|
|
3979
4159
|
if (signal.aborted)
|
|
3980
4160
|
return;
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
this.emitUpdate({ event, state: this.state, source: "replay" });
|
|
3984
|
-
}
|
|
3985
|
-
cursor = Math.max(cursor, this.state.lastSeq);
|
|
3986
|
-
this.setStatus("connected");
|
|
3987
|
-
}
|
|
3988
|
-
while (!signal.aborted) {
|
|
3989
|
-
try {
|
|
3990
|
-
if (this.sessionId && attempt > 0) {
|
|
3991
|
-
await refreshSession(this.sessionId);
|
|
3992
|
-
if (signal.aborted)
|
|
3993
|
-
return;
|
|
3994
|
-
}
|
|
3995
|
-
for await (const event of this.client.events.stream({
|
|
3996
|
-
cursor,
|
|
3997
|
-
sessionId: this.sessionId,
|
|
3998
|
-
signal,
|
|
3999
|
-
transportReconnect: false
|
|
4000
|
-
})) {
|
|
4001
|
-
if (signal.aborted)
|
|
4002
|
-
return;
|
|
4003
|
-
attempt = 0;
|
|
4004
|
-
this.setStatus("connected");
|
|
4005
|
-
if (event.seq > cursor + 1 && !this.sessionId) {
|
|
4006
|
-
const gap = await this.client.events.list({
|
|
4007
|
-
cursor,
|
|
4008
|
-
sessionId: this.sessionId,
|
|
4009
|
-
signal
|
|
4010
|
-
});
|
|
4011
|
-
if (signal.aborted)
|
|
4012
|
-
return;
|
|
4013
|
-
for (const missed of gap) {
|
|
4014
|
-
const beforeGap = this.state;
|
|
4015
|
-
this.state = applyEvent(this.state, missed);
|
|
4016
|
-
if (this.state !== beforeGap) {
|
|
4017
|
-
this.emitUpdate({ event: missed, state: this.state, source: "replay" });
|
|
4018
|
-
}
|
|
4019
|
-
}
|
|
4020
|
-
cursor = Math.max(cursor, this.state.lastSeq);
|
|
4021
|
-
}
|
|
4022
|
-
const before = this.state;
|
|
4023
|
-
this.state = this.sessionId ? applySessionStreamEvent(this.state, event) : applyEvent(this.state, event);
|
|
4024
|
-
cursor = Math.max(cursor, this.state.lastSeq);
|
|
4025
|
-
if (this.state !== before) {
|
|
4026
|
-
this.emitUpdate({ event, state: this.state, source: "live" });
|
|
4027
|
-
}
|
|
4028
|
-
}
|
|
4029
|
-
if (signal.aborted)
|
|
4030
|
-
return;
|
|
4031
|
-
this.setStatus("reconnecting");
|
|
4032
|
-
this.emitUpdate({ state: this.state, source: "reconnecting" });
|
|
4033
|
-
const delay = this.reconnectDelayMs(attempt);
|
|
4034
|
-
attempt += 1;
|
|
4035
|
-
cursor = this.state.lastSeq;
|
|
4036
|
-
if (!await this.waitForDelay(delay, signal))
|
|
4037
|
-
return;
|
|
4038
|
-
} catch (error) {
|
|
4039
|
-
if (error instanceof UnsupportedSessionEventSchemaVersionError) {
|
|
4040
|
-
throw error;
|
|
4041
|
-
}
|
|
4042
|
-
if (this.isAbortError(error) || signal.aborted)
|
|
4043
|
-
return;
|
|
4161
|
+
this.state = update.state;
|
|
4162
|
+
if (update.source === "reconnecting")
|
|
4044
4163
|
this.setStatus("reconnecting");
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4164
|
+
else if (update.source !== "replay")
|
|
4165
|
+
this.setStatus("connected");
|
|
4166
|
+
if (signal.aborted)
|
|
4167
|
+
return;
|
|
4168
|
+
if (update.source === "snapshot" && !this.options.sessionId)
|
|
4169
|
+
continue;
|
|
4170
|
+
this.options.onUpdate?.(update, this.generation);
|
|
4052
4171
|
}
|
|
4053
4172
|
} catch (error) {
|
|
4054
|
-
if (!
|
|
4173
|
+
if (!signal.aborted && !(error instanceof Error && error.name === "AbortError")) {
|
|
4055
4174
|
this.setStatus("error");
|
|
4056
|
-
this.onError?.(error, this.generation);
|
|
4175
|
+
this.options.onError?.(error, this.generation);
|
|
4057
4176
|
}
|
|
4058
4177
|
} finally {
|
|
4059
4178
|
this.running = false;
|
|
4060
|
-
if (signal.aborted && this.status !== "error")
|
|
4179
|
+
if (signal.aborted && this.status !== "error")
|
|
4061
4180
|
this.setStatus("idle");
|
|
4062
|
-
}
|
|
4063
4181
|
}
|
|
4064
4182
|
}
|
|
4065
|
-
isAbortError(error) {
|
|
4066
|
-
if (!error || typeof error !== "object")
|
|
4067
|
-
return false;
|
|
4068
|
-
if ("name" in error && error.name === "AbortError")
|
|
4069
|
-
return true;
|
|
4070
|
-
return false;
|
|
4071
|
-
}
|
|
4072
|
-
waitForDelay(ms, signal) {
|
|
4073
|
-
if (ms <= 0)
|
|
4074
|
-
return Promise.resolve(!signal.aborted);
|
|
4075
|
-
return new Promise((resolve) => {
|
|
4076
|
-
if (signal.aborted) {
|
|
4077
|
-
resolve(false);
|
|
4078
|
-
return;
|
|
4079
|
-
}
|
|
4080
|
-
const timer = setTimeout(() => {
|
|
4081
|
-
signal.removeEventListener("abort", onAbort);
|
|
4082
|
-
resolve(!signal.aborted);
|
|
4083
|
-
}, ms);
|
|
4084
|
-
const onAbort = () => {
|
|
4085
|
-
clearTimeout(timer);
|
|
4086
|
-
resolve(false);
|
|
4087
|
-
};
|
|
4088
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
4089
|
-
});
|
|
4090
|
-
}
|
|
4091
4183
|
}
|
|
4092
|
-
var DEFAULT_RECONNECT_DELAY_MS = (attempt) => Math.min(30000, 250 * 2 ** Math.max(0, attempt));
|
|
4093
4184
|
var init_session_sync_controller = __esm(() => {
|
|
4094
4185
|
init_reducer();
|
|
4095
|
-
|
|
4186
|
+
init_sync();
|
|
4096
4187
|
});
|
|
4097
4188
|
|
|
4098
4189
|
// ../../packages/client/src/state/index.ts
|
|
4099
4190
|
var init_state = __esm(() => {
|
|
4100
4191
|
init_reducer();
|
|
4101
4192
|
init_selectors();
|
|
4102
|
-
init_sync();
|
|
4103
4193
|
init_session_sync_controller();
|
|
4104
4194
|
});
|
|
4105
4195
|
|
|
@@ -4158,12 +4248,12 @@ async function handleDiagnosticCommand(slash, host, emit, readPresentation) {
|
|
|
4158
4248
|
const estimatedTokens = Math.max(1, Math.ceil(text.length / 4));
|
|
4159
4249
|
const [memory, jobsResult, settings] = await Promise.all([
|
|
4160
4250
|
client.system.listMemory({ cwd }).catch(() => ({ entries: [] })),
|
|
4161
|
-
client.jobs.list({ sessionId, includeFinished: true, limit: 100 }).then((
|
|
4251
|
+
client.jobs.list({ sessionId, includeFinished: true, limit: 100 }).then((listed) => ({ jobs: listed.jobs, errors: listed.errors })).catch((error) => ({
|
|
4162
4252
|
error: error instanceof Error ? error.message : String(error)
|
|
4163
4253
|
})),
|
|
4164
4254
|
client.system.getSettings().catch(() => ({}))
|
|
4165
4255
|
]);
|
|
4166
|
-
const jobsSummary = "jobs" in jobsResult ?
|
|
4256
|
+
const jobsSummary = "jobs" in jobsResult ? `${jobsResult.jobs.length}${"errors" in jobsResult && jobsResult.errors.length ? ` (${jobsResult.errors.map((error) => `${error.kind}: ${error.message}`).join("; ")})` : ""}` : `unavailable (${jobsResult.error})`;
|
|
4167
4257
|
emit([
|
|
4168
4258
|
"Session stats:",
|
|
4169
4259
|
`- messages: ${messageCount}`,
|
|
@@ -4178,20 +4268,22 @@ async function handleDiagnosticCommand(slash, host, emit, readPresentation) {
|
|
|
4178
4268
|
if (slash.name === "/agents") {
|
|
4179
4269
|
if (!sessionId)
|
|
4180
4270
|
return "handled";
|
|
4181
|
-
const
|
|
4271
|
+
const listed = await client.jobs.list({
|
|
4182
4272
|
sessionId,
|
|
4183
4273
|
kinds: ["agent"],
|
|
4184
4274
|
includeFinished: true,
|
|
4185
4275
|
limit: 100
|
|
4186
4276
|
});
|
|
4187
|
-
|
|
4277
|
+
const agents = listed.jobs;
|
|
4278
|
+
if (agents.length === 0 && !listed.errors.length) {
|
|
4188
4279
|
emit("No Agent Jobs.");
|
|
4189
4280
|
return "handled";
|
|
4190
4281
|
}
|
|
4191
4282
|
emit([
|
|
4192
4283
|
`Agent Jobs (${agents.length}):`,
|
|
4193
4284
|
"",
|
|
4194
|
-
...agents.map((job) => ` ${job.id} [${job.status}] ${job.label}`)
|
|
4285
|
+
...agents.map((job) => ` ${job.id} [${job.status}] ${job.label}`),
|
|
4286
|
+
...listed.errors.map((error) => `${error.kind}: ${error.message}`)
|
|
4195
4287
|
].join(`
|
|
4196
4288
|
`));
|
|
4197
4289
|
return "handled";
|
|
@@ -4201,11 +4293,11 @@ async function handleDiagnosticCommand(slash, host, emit, readPresentation) {
|
|
|
4201
4293
|
client.auth.getStatus().catch(() => null),
|
|
4202
4294
|
client.system.listMemory({ cwd }).catch(() => ({ directory: "(unavailable)", entries: [] })),
|
|
4203
4295
|
sessionId ? client.system.getSessionMcp(sessionId).catch(() => []) : Promise.resolve([]),
|
|
4204
|
-
sessionId ? client.jobs.list({ sessionId, includeFinished: true, limit: 100 }).then((
|
|
4296
|
+
sessionId ? client.jobs.list({ sessionId, includeFinished: true, limit: 100 }).then((listed) => ({ jobs: listed.jobs, errors: listed.errors })).catch((error) => ({
|
|
4205
4297
|
error: error instanceof Error ? error.message : String(error)
|
|
4206
4298
|
})) : Promise.resolve({ jobs: [] })
|
|
4207
4299
|
]);
|
|
4208
|
-
const jobsSummary = "jobs" in jobsResult ?
|
|
4300
|
+
const jobsSummary = "jobs" in jobsResult ? `${jobsResult.jobs.length}${"errors" in jobsResult && jobsResult.errors.length ? ` (${jobsResult.errors.map((error) => `${error.kind}: ${error.message}`).join("; ")})` : ""}` : `unavailable (${jobsResult.error})`;
|
|
4209
4301
|
const bucket = sessionId ? clientState.buckets[sessionId] : undefined;
|
|
4210
4302
|
const diagnostics = await host.getRuntimeDiagnostics?.();
|
|
4211
4303
|
const runtime = diagnostics?.runtime ?? "(not provided by this host)";
|
|
@@ -4300,41 +4392,44 @@ async function handleJobCommand(slash, host, emit, readPresentation) {
|
|
|
4300
4392
|
if (!sessionId)
|
|
4301
4393
|
return "handled";
|
|
4302
4394
|
const args = slash.args.trim();
|
|
4303
|
-
const [sub, id, ...extra] = args.split(/\s+/).filter(Boolean);
|
|
4304
|
-
if ((!sub || sub === "list") && !
|
|
4395
|
+
const [sub, kind, id, runId, ...extra] = args.split(/\s+/).filter(Boolean);
|
|
4396
|
+
if ((!sub || sub === "list") && !kind) {
|
|
4305
4397
|
await readPresentation(`jobs:${sessionId}`, "Jobs", async () => {
|
|
4306
|
-
const
|
|
4398
|
+
const listed = await client.jobs.list({
|
|
4307
4399
|
sessionId,
|
|
4308
4400
|
includeFinished: true,
|
|
4309
4401
|
limit: 100
|
|
4310
4402
|
});
|
|
4311
|
-
|
|
4403
|
+
const { jobs, errors } = listed;
|
|
4404
|
+
if (jobs.length === 0 && errors.length === 0)
|
|
4312
4405
|
return "No Jobs.";
|
|
4313
4406
|
return [
|
|
4314
4407
|
`Jobs (${jobs.length}):`,
|
|
4315
4408
|
"",
|
|
4316
|
-
...jobs.map((job) => ` ${job.id} [${job.status}] ${job.kind}: ${job.label}`)
|
|
4409
|
+
...jobs.map((job) => ` ${job.id} [${job.status}] ${job.kind}: ${job.label}${job.target.kind === "agent" ? ` runId=${job.target.runId}` : ""}`),
|
|
4410
|
+
...errors.map((error) => `${error.kind}: ${error.message}`)
|
|
4317
4411
|
].join(`
|
|
4318
4412
|
`);
|
|
4319
4413
|
});
|
|
4320
4414
|
return "handled";
|
|
4321
4415
|
}
|
|
4322
|
-
|
|
4416
|
+
const target = kind === "agent" && id && runId ? { kind, jobId: id, runId } : ["shell", "terminal", "workflow", "dream"].includes(kind ?? "") && id && !runId ? { kind, jobId: id } : undefined;
|
|
4417
|
+
if (sub === "show" && target && extra.length === 0) {
|
|
4323
4418
|
await readPresentation(`job:${sessionId}:${id}`, "Jobs", async () => {
|
|
4324
|
-
const result = await client.jobs.read(
|
|
4419
|
+
const result = await client.jobs.read(target, { sessionId });
|
|
4325
4420
|
return formatJobReadResult(result);
|
|
4326
4421
|
});
|
|
4327
4422
|
return "handled";
|
|
4328
4423
|
}
|
|
4329
|
-
if (sub === "cancel" &&
|
|
4330
|
-
const snapshot = await client.jobs.cancel(
|
|
4424
|
+
if (sub === "cancel" && target && extra.length === 0) {
|
|
4425
|
+
const snapshot = await client.jobs.cancel(target, {
|
|
4331
4426
|
sessionId,
|
|
4332
4427
|
reason: "Cancelled from slash command"
|
|
4333
4428
|
});
|
|
4334
4429
|
emit(`Job ${snapshot.id} status: ${snapshot.status}.`);
|
|
4335
4430
|
return "handled";
|
|
4336
4431
|
}
|
|
4337
|
-
emit("Usage: /jobs [list | show ID | cancel ID]");
|
|
4432
|
+
emit("Usage: /jobs [list | show KIND ID [RUN_ID] | cancel KIND ID [RUN_ID]]");
|
|
4338
4433
|
return "handled";
|
|
4339
4434
|
}
|
|
4340
4435
|
|
|
@@ -5865,7 +5960,12 @@ async function executeTuiAction(action, ctx) {
|
|
|
5865
5960
|
ctx.jobControlAbortRef.current = controller;
|
|
5866
5961
|
let response;
|
|
5867
5962
|
try {
|
|
5868
|
-
response = await client.jobs.cancel(
|
|
5963
|
+
response = await client.jobs.cancel((() => {
|
|
5964
|
+
const selected = ctx.jobStateRef.current.jobs.find((job) => job.id === action.job_id)?.target;
|
|
5965
|
+
if (!selected)
|
|
5966
|
+
throw new Error("\u8BF7\u5148\u9009\u62E9\u4E00\u4E2A\u5F53\u524D\u4EFB\u52A1\u3002");
|
|
5967
|
+
return selected;
|
|
5968
|
+
})(), {
|
|
5869
5969
|
sessionId: currentSessionId,
|
|
5870
5970
|
reason: action.reason
|
|
5871
5971
|
}, { signal: controller.signal });
|
|
@@ -5909,7 +6009,12 @@ async function executeTuiAction(action, ctx) {
|
|
|
5909
6009
|
const controller = new AbortController;
|
|
5910
6010
|
ctx.jobControlAbortRef.current = controller;
|
|
5911
6011
|
try {
|
|
5912
|
-
await client.jobs.send(
|
|
6012
|
+
await client.jobs.send((() => {
|
|
6013
|
+
const selected = ctx.jobStateRef.current.jobs.find((job) => job.id === action.job_id)?.target;
|
|
6014
|
+
if (!selected)
|
|
6015
|
+
throw new Error("\u8BF7\u5148\u9009\u62E9\u4E00\u4E2A\u5F53\u524D\u4EFB\u52A1\u3002");
|
|
6016
|
+
return selected;
|
|
6017
|
+
})(), {
|
|
5913
6018
|
sessionId: currentSessionId,
|
|
5914
6019
|
data: action.data
|
|
5915
6020
|
}, { signal: controller.signal });
|
|
@@ -6142,9 +6247,9 @@ function useServerSync(config, onError) {
|
|
|
6142
6247
|
});
|
|
6143
6248
|
if (activeSessionIdRef.current !== sessionId || jobGenerationRef.current !== generation)
|
|
6144
6249
|
return;
|
|
6145
|
-
const validated = validateJobSnapshots(response, sessionId);
|
|
6146
|
-
if (validated.error) {
|
|
6147
|
-
const validationError = validated.error;
|
|
6250
|
+
const validated = validateJobSnapshots(response.jobs, sessionId);
|
|
6251
|
+
if (validated.error || response.errors.length) {
|
|
6252
|
+
const validationError = validated.error ?? response.errors.map((error) => `${error.kind}: ${error.message}`).join("; ");
|
|
6148
6253
|
const now = Date.now();
|
|
6149
6254
|
setJobState((current) => {
|
|
6150
6255
|
const next = validated.jobs.length > 0 ? {
|
|
@@ -6193,7 +6298,10 @@ function useServerSync(config, onError) {
|
|
|
6193
6298
|
jobDetailStateRef.current = loading;
|
|
6194
6299
|
setJobDetailState(loading);
|
|
6195
6300
|
try {
|
|
6196
|
-
const
|
|
6301
|
+
const selected = jobStateRef.current.jobs.find((job) => job.id === jobId)?.target;
|
|
6302
|
+
if (!selected)
|
|
6303
|
+
throw new Error("\u8BF7\u5148\u9009\u62E9\u4E00\u4E2A\u5F53\u524D\u4EFB\u52A1\u3002");
|
|
6304
|
+
const response = await client.jobs.read(selected, {
|
|
6197
6305
|
sessionId,
|
|
6198
6306
|
signal: controller.signal
|
|
6199
6307
|
});
|
|
@@ -10446,4 +10554,4 @@ try {
|
|
|
10446
10554
|
process.exit(1);
|
|
10447
10555
|
}
|
|
10448
10556
|
|
|
10449
|
-
//# debugId=
|
|
10557
|
+
//# debugId=D9F985CEDC561F0364756E2164756E21
|