nextclaw 0.9.3 → 0.9.4
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/cli/index.js +674 -251
- package/package.json +2 -2
- package/ui-dist/assets/{ChannelsList-BZck3Ik3.js → ChannelsList-Bga6n85j.js} +1 -1
- package/ui-dist/assets/ChatPage-B-Yk3kkv.js +32 -0
- package/ui-dist/assets/{DocBrowser-C3BZ2Dbd.js → DocBrowser-dv57PRp5.js} +1 -1
- package/ui-dist/assets/{MarketplacePage-eXKEwWed.js → MarketplacePage-j6p73Hjo.js} +1 -1
- package/ui-dist/assets/{ModelConfig-CYJ48kje.js → ModelConfig-BiKSDp5h.js} +1 -1
- package/ui-dist/assets/{ProvidersList-DMOGZZYe.js → ProvidersList-B7ZfRUkD.js} +1 -1
- package/ui-dist/assets/{RuntimeConfig-hlfOi-Lb.js → RuntimeConfig-Bpt9UNb6.js} +1 -1
- package/ui-dist/assets/{SecretsConfig-CyrpdMr6.js → SecretsConfig-Ds00G-_O.js} +2 -2
- package/ui-dist/assets/{SessionsConfig-nnFt5469.js → SessionsConfig-Mjet4opU.js} +1 -1
- package/ui-dist/assets/{card-BLYfBPjZ.js → card-C7JJ5BGA.js} +1 -1
- package/ui-dist/assets/index-BiJ2xs5X.css +1 -0
- package/ui-dist/assets/{index-3FRIgt2I.js → index-Cb9xiqC5.js} +2 -2
- package/ui-dist/assets/{label-BTTmp29j.js → label-DHJKdaUl.js} +1 -1
- package/ui-dist/assets/{logos-DdneJrRE.js → logos-fPO_amyL.js} +1 -1
- package/ui-dist/assets/{page-layout-BRvg8R6t.js → page-layout-CF0JQsWW.js} +1 -1
- package/ui-dist/assets/{switch-DwrbUdK4.js → switch-C1hgy-fE.js} +1 -1
- package/ui-dist/assets/{tabs-custom-D2ymr0Hn.js → tabs-custom-OyoLf5ZM.js} +1 -1
- package/ui-dist/assets/useConfig-D_G46zbo.js +6 -0
- package/ui-dist/assets/{useConfirmDialog-BqkRDmSt.js → useConfirmDialog-_0u6i3cI.js} +1 -1
- package/ui-dist/index.html +2 -2
- package/ui-dist/assets/ChatPage-CaEcg_CO.js +0 -32
- package/ui-dist/assets/index-CWTKcaQW.css +0 -1
- package/ui-dist/assets/useConfig-CFA_BuWu.js +0 -6
package/dist/cli/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
loadConfig as loadConfig7,
|
|
10
10
|
saveConfig as saveConfig6,
|
|
11
11
|
getConfigPath as getConfigPath4,
|
|
12
|
-
getDataDir as
|
|
12
|
+
getDataDir as getDataDir8,
|
|
13
13
|
ConfigSchema as ConfigSchema2,
|
|
14
14
|
getWorkspacePath as getWorkspacePath6,
|
|
15
15
|
expandHome as expandHome2,
|
|
@@ -26,8 +26,8 @@ import {
|
|
|
26
26
|
resolvePluginChannelMessageToolHints as resolvePluginChannelMessageToolHints2,
|
|
27
27
|
setPluginRuntimeBridge as setPluginRuntimeBridge2
|
|
28
28
|
} from "@nextclaw/openclaw-compat";
|
|
29
|
-
import { existsSync as
|
|
30
|
-
import { join as
|
|
29
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
30
|
+
import { join as join7, resolve as resolve9 } from "path";
|
|
31
31
|
import { createInterface as createInterface2 } from "readline";
|
|
32
32
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
33
33
|
import { spawn as spawn3 } from "child_process";
|
|
@@ -2128,6 +2128,11 @@ var DiagnosticsCommands = class {
|
|
|
2128
2128
|
issues.push(`Managed service health check failed: ${managedHealth.detail}`);
|
|
2129
2129
|
recommendations.push(`Check logs at ${serviceState?.logPath ?? resolveServiceLogPath()}.`);
|
|
2130
2130
|
}
|
|
2131
|
+
if (running && serviceState?.startupState === "degraded" && managedHealth.state !== "ok") {
|
|
2132
|
+
const startupHint = serviceState.startupLastProbeError ? ` (${serviceState.startupLastProbeError})` : "";
|
|
2133
|
+
issues.push(`Service is in degraded startup state${startupHint}.`);
|
|
2134
|
+
recommendations.push(`Wait and re-check ${APP_NAME} status; if it does not recover, inspect logs and restart.`);
|
|
2135
|
+
}
|
|
2131
2136
|
if (!running) {
|
|
2132
2137
|
recommendations.push(`Run ${APP_NAME} start to launch the service.`);
|
|
2133
2138
|
}
|
|
@@ -2244,8 +2249,8 @@ import {
|
|
|
2244
2249
|
stopPluginChannelGateways
|
|
2245
2250
|
} from "@nextclaw/openclaw-compat";
|
|
2246
2251
|
import { startUiServer } from "@nextclaw/server";
|
|
2247
|
-
import { closeSync, cpSync, existsSync as
|
|
2248
|
-
import { dirname, isAbsolute as isAbsolute2, join as
|
|
2252
|
+
import { appendFileSync, closeSync, cpSync, existsSync as existsSync8, mkdirSync as mkdirSync4, openSync, rmSync as rmSync3 } from "fs";
|
|
2253
|
+
import { dirname, isAbsolute as isAbsolute2, join as join5, relative, resolve as resolve7 } from "path";
|
|
2249
2254
|
import { spawn as spawn2 } from "child_process";
|
|
2250
2255
|
import { request as httpRequest } from "http";
|
|
2251
2256
|
import { request as httpsRequest } from "https";
|
|
@@ -2980,6 +2985,497 @@ var GatewayAgentRuntimePool = class {
|
|
|
2980
2985
|
}
|
|
2981
2986
|
};
|
|
2982
2987
|
|
|
2988
|
+
// src/cli/commands/ui-chat-run-coordinator.ts
|
|
2989
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
2990
|
+
import { join as join4 } from "path";
|
|
2991
|
+
import {
|
|
2992
|
+
getDataDir as getDataDir5,
|
|
2993
|
+
parseAgentScopedSessionKey as parseAgentScopedSessionKey2,
|
|
2994
|
+
safeFilename
|
|
2995
|
+
} from "@nextclaw/core";
|
|
2996
|
+
var RUNS_DIR = join4(getDataDir5(), "runs");
|
|
2997
|
+
var NON_TERMINAL_STATES = /* @__PURE__ */ new Set(["queued", "running"]);
|
|
2998
|
+
function createRunId() {
|
|
2999
|
+
const now = Date.now().toString(36);
|
|
3000
|
+
const rand = Math.random().toString(36).slice(2, 10);
|
|
3001
|
+
return `run-${now}-${rand}`;
|
|
3002
|
+
}
|
|
3003
|
+
function isRecord(value) {
|
|
3004
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3005
|
+
}
|
|
3006
|
+
function readOptionalString(value) {
|
|
3007
|
+
if (typeof value !== "string") {
|
|
3008
|
+
return void 0;
|
|
3009
|
+
}
|
|
3010
|
+
const trimmed = value.trim();
|
|
3011
|
+
return trimmed || void 0;
|
|
3012
|
+
}
|
|
3013
|
+
function isAbortError(error) {
|
|
3014
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
3015
|
+
return true;
|
|
3016
|
+
}
|
|
3017
|
+
if (error instanceof Error) {
|
|
3018
|
+
if (error.name === "AbortError") {
|
|
3019
|
+
return true;
|
|
3020
|
+
}
|
|
3021
|
+
const message = error.message.toLowerCase();
|
|
3022
|
+
if (message.includes("aborted") || message.includes("abort")) {
|
|
3023
|
+
return true;
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
3026
|
+
return false;
|
|
3027
|
+
}
|
|
3028
|
+
function cloneEvent(event) {
|
|
3029
|
+
return JSON.parse(JSON.stringify(event));
|
|
3030
|
+
}
|
|
3031
|
+
var UiChatRunCoordinator = class {
|
|
3032
|
+
constructor(options) {
|
|
3033
|
+
this.options = options;
|
|
3034
|
+
mkdirSync3(RUNS_DIR, { recursive: true });
|
|
3035
|
+
this.loadPersistedRuns();
|
|
3036
|
+
}
|
|
3037
|
+
runs = /* @__PURE__ */ new Map();
|
|
3038
|
+
sessionRuns = /* @__PURE__ */ new Map();
|
|
3039
|
+
startRun(input) {
|
|
3040
|
+
const request = this.resolveRequest(input);
|
|
3041
|
+
const stopCapability = this.options.runtimePool.supportsTurnAbort({
|
|
3042
|
+
sessionKey: request.sessionKey,
|
|
3043
|
+
agentId: request.agentId,
|
|
3044
|
+
channel: request.channel,
|
|
3045
|
+
chatId: request.chatId,
|
|
3046
|
+
metadata: request.metadata
|
|
3047
|
+
});
|
|
3048
|
+
const run = {
|
|
3049
|
+
runId: request.runId,
|
|
3050
|
+
sessionKey: request.sessionKey,
|
|
3051
|
+
...request.agentId ? { agentId: request.agentId } : {},
|
|
3052
|
+
...request.model ? { model: request.model, requestedModel: request.model } : {},
|
|
3053
|
+
state: "queued",
|
|
3054
|
+
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3055
|
+
stopSupported: stopCapability.supported,
|
|
3056
|
+
...stopCapability.reason ? { stopReason: stopCapability.reason } : {},
|
|
3057
|
+
events: [],
|
|
3058
|
+
waiters: /* @__PURE__ */ new Set(),
|
|
3059
|
+
abortController: null,
|
|
3060
|
+
cancelRequested: false
|
|
3061
|
+
};
|
|
3062
|
+
this.runs.set(run.runId, run);
|
|
3063
|
+
this.bindRunToSession(run.sessionKey, run.runId);
|
|
3064
|
+
this.syncSessionRunState(run);
|
|
3065
|
+
this.persistRun(run);
|
|
3066
|
+
this.emitRunUpdated(run);
|
|
3067
|
+
void this.executeRun(run, request);
|
|
3068
|
+
return this.toRunView(run);
|
|
3069
|
+
}
|
|
3070
|
+
getRun(params) {
|
|
3071
|
+
const run = this.getRunRecord(params.runId);
|
|
3072
|
+
return run ? this.toRunView(run) : null;
|
|
3073
|
+
}
|
|
3074
|
+
listRuns(params = {}) {
|
|
3075
|
+
const sessionKey = readOptionalString(params.sessionKey);
|
|
3076
|
+
const stateFilter = Array.isArray(params.states) && params.states.length > 0 ? new Set(params.states) : null;
|
|
3077
|
+
const limit = Number.isFinite(params.limit) ? Math.max(0, Math.trunc(params.limit)) : 0;
|
|
3078
|
+
const records = Array.from(this.runs.values()).filter((run) => {
|
|
3079
|
+
if (sessionKey && run.sessionKey !== sessionKey) {
|
|
3080
|
+
return false;
|
|
3081
|
+
}
|
|
3082
|
+
if (stateFilter && !stateFilter.has(run.state)) {
|
|
3083
|
+
return false;
|
|
3084
|
+
}
|
|
3085
|
+
return true;
|
|
3086
|
+
}).sort((left, right) => Date.parse(right.requestedAt) - Date.parse(left.requestedAt));
|
|
3087
|
+
const total = records.length;
|
|
3088
|
+
const runs = (limit > 0 ? records.slice(0, limit) : records).map((run) => this.toRunView(run));
|
|
3089
|
+
return { runs, total };
|
|
3090
|
+
}
|
|
3091
|
+
async *streamRun(params) {
|
|
3092
|
+
const run = this.getRunRecord(params.runId);
|
|
3093
|
+
if (!run) {
|
|
3094
|
+
throw new Error(`chat run not found: ${params.runId}`);
|
|
3095
|
+
}
|
|
3096
|
+
let cursor = Number.isFinite(params.fromEventIndex) ? Math.max(0, Math.trunc(params.fromEventIndex)) : 0;
|
|
3097
|
+
while (true) {
|
|
3098
|
+
while (cursor < run.events.length) {
|
|
3099
|
+
yield cloneEvent(run.events[cursor]);
|
|
3100
|
+
cursor += 1;
|
|
3101
|
+
}
|
|
3102
|
+
if (!NON_TERMINAL_STATES.has(run.state)) {
|
|
3103
|
+
return;
|
|
3104
|
+
}
|
|
3105
|
+
if (params.signal?.aborted) {
|
|
3106
|
+
return;
|
|
3107
|
+
}
|
|
3108
|
+
await this.waitForRunUpdate(run, params.signal);
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
async stopRun(params) {
|
|
3112
|
+
const runId = readOptionalString(params.runId) ?? "";
|
|
3113
|
+
if (!runId) {
|
|
3114
|
+
return {
|
|
3115
|
+
stopped: false,
|
|
3116
|
+
runId: "",
|
|
3117
|
+
reason: "runId is required"
|
|
3118
|
+
};
|
|
3119
|
+
}
|
|
3120
|
+
const run = this.getRunRecord(runId);
|
|
3121
|
+
if (!run) {
|
|
3122
|
+
return {
|
|
3123
|
+
stopped: false,
|
|
3124
|
+
runId,
|
|
3125
|
+
...readOptionalString(params.sessionKey) ? { sessionKey: readOptionalString(params.sessionKey) } : {},
|
|
3126
|
+
reason: "run not found or already completed"
|
|
3127
|
+
};
|
|
3128
|
+
}
|
|
3129
|
+
const requestedSessionKey = readOptionalString(params.sessionKey);
|
|
3130
|
+
if (requestedSessionKey && requestedSessionKey !== run.sessionKey) {
|
|
3131
|
+
return {
|
|
3132
|
+
stopped: false,
|
|
3133
|
+
runId,
|
|
3134
|
+
sessionKey: run.sessionKey,
|
|
3135
|
+
reason: "session key mismatch"
|
|
3136
|
+
};
|
|
3137
|
+
}
|
|
3138
|
+
if (!run.stopSupported) {
|
|
3139
|
+
return {
|
|
3140
|
+
stopped: false,
|
|
3141
|
+
runId,
|
|
3142
|
+
sessionKey: run.sessionKey,
|
|
3143
|
+
reason: run.stopReason ?? "run stop is not supported"
|
|
3144
|
+
};
|
|
3145
|
+
}
|
|
3146
|
+
if (!NON_TERMINAL_STATES.has(run.state)) {
|
|
3147
|
+
return {
|
|
3148
|
+
stopped: false,
|
|
3149
|
+
runId,
|
|
3150
|
+
sessionKey: run.sessionKey,
|
|
3151
|
+
reason: `run already ${run.state}`
|
|
3152
|
+
};
|
|
3153
|
+
}
|
|
3154
|
+
run.cancelRequested = true;
|
|
3155
|
+
if (run.abortController) {
|
|
3156
|
+
run.abortController.abort(new Error("chat turn stopped by user"));
|
|
3157
|
+
}
|
|
3158
|
+
return {
|
|
3159
|
+
stopped: true,
|
|
3160
|
+
runId,
|
|
3161
|
+
sessionKey: run.sessionKey
|
|
3162
|
+
};
|
|
3163
|
+
}
|
|
3164
|
+
resolveRequest(input) {
|
|
3165
|
+
const message = readOptionalString(input.message) ?? "";
|
|
3166
|
+
const sessionKey = readOptionalString(input.sessionKey) ?? `ui:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 8)}`;
|
|
3167
|
+
const explicitAgentId = readOptionalString(input.agentId);
|
|
3168
|
+
const parsedAgentId = parseAgentScopedSessionKey2(sessionKey)?.agentId;
|
|
3169
|
+
const agentId = explicitAgentId ?? readOptionalString(parsedAgentId);
|
|
3170
|
+
const model = readOptionalString(input.model);
|
|
3171
|
+
const metadata = isRecord(input.metadata) ? { ...input.metadata } : {};
|
|
3172
|
+
if (model) {
|
|
3173
|
+
metadata.model = model;
|
|
3174
|
+
}
|
|
3175
|
+
const runId = readOptionalString(input.runId) ?? createRunId();
|
|
3176
|
+
return {
|
|
3177
|
+
runId,
|
|
3178
|
+
message,
|
|
3179
|
+
sessionKey,
|
|
3180
|
+
...agentId ? { agentId } : {},
|
|
3181
|
+
...model ? { model } : {},
|
|
3182
|
+
metadata,
|
|
3183
|
+
channel: readOptionalString(input.channel) ?? "ui",
|
|
3184
|
+
chatId: readOptionalString(input.chatId) ?? "web-ui"
|
|
3185
|
+
};
|
|
3186
|
+
}
|
|
3187
|
+
async executeRun(run, request) {
|
|
3188
|
+
if (run.cancelRequested) {
|
|
3189
|
+
this.transitionState(run, "aborted");
|
|
3190
|
+
return;
|
|
3191
|
+
}
|
|
3192
|
+
this.transitionState(run, "running");
|
|
3193
|
+
const abortController = run.stopSupported ? new AbortController() : null;
|
|
3194
|
+
run.abortController = abortController;
|
|
3195
|
+
const assistantDeltaParts = [];
|
|
3196
|
+
try {
|
|
3197
|
+
const reply = await this.options.runtimePool.processDirect({
|
|
3198
|
+
content: request.message,
|
|
3199
|
+
sessionKey: request.sessionKey,
|
|
3200
|
+
channel: request.channel,
|
|
3201
|
+
chatId: request.chatId,
|
|
3202
|
+
agentId: request.agentId,
|
|
3203
|
+
metadata: request.metadata,
|
|
3204
|
+
...abortController ? { abortSignal: abortController.signal } : {},
|
|
3205
|
+
onAssistantDelta: (delta) => {
|
|
3206
|
+
if (typeof delta !== "string" || delta.length === 0) {
|
|
3207
|
+
return;
|
|
3208
|
+
}
|
|
3209
|
+
assistantDeltaParts.push(delta);
|
|
3210
|
+
this.pushStreamEvent(run, { type: "delta", delta });
|
|
3211
|
+
},
|
|
3212
|
+
onSessionEvent: (event) => {
|
|
3213
|
+
this.pushStreamEvent(run, {
|
|
3214
|
+
type: "session_event",
|
|
3215
|
+
event: this.mapSessionEvent(event)
|
|
3216
|
+
});
|
|
3217
|
+
}
|
|
3218
|
+
});
|
|
3219
|
+
this.pushStreamEvent(run, {
|
|
3220
|
+
type: "final",
|
|
3221
|
+
result: {
|
|
3222
|
+
reply,
|
|
3223
|
+
sessionKey: request.sessionKey,
|
|
3224
|
+
...request.agentId ? { agentId: request.agentId } : {},
|
|
3225
|
+
...request.model ? { model: request.model } : {}
|
|
3226
|
+
}
|
|
3227
|
+
});
|
|
3228
|
+
run.reply = reply;
|
|
3229
|
+
this.transitionState(run, "completed");
|
|
3230
|
+
} catch (error) {
|
|
3231
|
+
const aborted = (abortController?.signal.aborted ?? false) || isAbortError(error);
|
|
3232
|
+
if (aborted) {
|
|
3233
|
+
const partialReply = assistantDeltaParts.join("");
|
|
3234
|
+
if (partialReply.trim().length > 0) {
|
|
3235
|
+
this.persistAbortedAssistantReply(run.sessionKey, partialReply);
|
|
3236
|
+
}
|
|
3237
|
+
this.pushStreamEvent(run, {
|
|
3238
|
+
type: "final",
|
|
3239
|
+
result: {
|
|
3240
|
+
reply: partialReply,
|
|
3241
|
+
sessionKey: request.sessionKey,
|
|
3242
|
+
...request.agentId ? { agentId: request.agentId } : {},
|
|
3243
|
+
...request.model ? { model: request.model } : {}
|
|
3244
|
+
}
|
|
3245
|
+
});
|
|
3246
|
+
run.reply = partialReply;
|
|
3247
|
+
this.transitionState(run, "aborted", {
|
|
3248
|
+
error: abortController?.signal.reason instanceof Error ? abortController.signal.reason.message : readOptionalString(abortController?.signal.reason)
|
|
3249
|
+
});
|
|
3250
|
+
return;
|
|
3251
|
+
}
|
|
3252
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3253
|
+
this.pushStreamEvent(run, {
|
|
3254
|
+
type: "error",
|
|
3255
|
+
error: errorMessage
|
|
3256
|
+
});
|
|
3257
|
+
this.transitionState(run, "failed", { error: errorMessage });
|
|
3258
|
+
} finally {
|
|
3259
|
+
run.abortController = null;
|
|
3260
|
+
this.persistRun(run);
|
|
3261
|
+
this.notifyRunWaiters(run);
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
transitionState(run, next, options = {}) {
|
|
3265
|
+
run.state = next;
|
|
3266
|
+
if (next === "running") {
|
|
3267
|
+
run.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3268
|
+
}
|
|
3269
|
+
if (!NON_TERMINAL_STATES.has(next)) {
|
|
3270
|
+
run.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3271
|
+
}
|
|
3272
|
+
if (options.error) {
|
|
3273
|
+
run.error = options.error;
|
|
3274
|
+
} else if (next === "completed") {
|
|
3275
|
+
run.error = void 0;
|
|
3276
|
+
}
|
|
3277
|
+
this.syncSessionRunState(run);
|
|
3278
|
+
this.persistRun(run);
|
|
3279
|
+
this.emitRunUpdated(run);
|
|
3280
|
+
this.notifyRunWaiters(run);
|
|
3281
|
+
}
|
|
3282
|
+
pushStreamEvent(run, event) {
|
|
3283
|
+
run.events.push(event);
|
|
3284
|
+
this.persistRun(run);
|
|
3285
|
+
this.notifyRunWaiters(run);
|
|
3286
|
+
this.emitRunUpdated(run);
|
|
3287
|
+
}
|
|
3288
|
+
waitForRunUpdate(run, signal) {
|
|
3289
|
+
return new Promise((resolve10) => {
|
|
3290
|
+
const wake = () => {
|
|
3291
|
+
cleanup();
|
|
3292
|
+
resolve10();
|
|
3293
|
+
};
|
|
3294
|
+
const onAbort = () => {
|
|
3295
|
+
cleanup();
|
|
3296
|
+
resolve10();
|
|
3297
|
+
};
|
|
3298
|
+
const cleanup = () => {
|
|
3299
|
+
run.waiters.delete(wake);
|
|
3300
|
+
signal?.removeEventListener("abort", onAbort);
|
|
3301
|
+
};
|
|
3302
|
+
run.waiters.add(wake);
|
|
3303
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
3304
|
+
});
|
|
3305
|
+
}
|
|
3306
|
+
notifyRunWaiters(run) {
|
|
3307
|
+
if (run.waiters.size === 0) {
|
|
3308
|
+
return;
|
|
3309
|
+
}
|
|
3310
|
+
const waiters = Array.from(run.waiters);
|
|
3311
|
+
run.waiters.clear();
|
|
3312
|
+
for (const wake of waiters) {
|
|
3313
|
+
wake();
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
bindRunToSession(sessionKey, runId) {
|
|
3317
|
+
const existing = this.sessionRuns.get(sessionKey);
|
|
3318
|
+
if (existing) {
|
|
3319
|
+
existing.add(runId);
|
|
3320
|
+
return;
|
|
3321
|
+
}
|
|
3322
|
+
this.sessionRuns.set(sessionKey, /* @__PURE__ */ new Set([runId]));
|
|
3323
|
+
}
|
|
3324
|
+
syncSessionRunState(run) {
|
|
3325
|
+
const session = this.options.sessionManager.getOrCreate(run.sessionKey);
|
|
3326
|
+
const metadata = session.metadata;
|
|
3327
|
+
metadata.ui_last_run_id = run.runId;
|
|
3328
|
+
metadata.ui_last_run_state = run.state;
|
|
3329
|
+
metadata.ui_last_run_updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
3330
|
+
if (NON_TERMINAL_STATES.has(run.state)) {
|
|
3331
|
+
metadata.ui_active_run_id = run.runId;
|
|
3332
|
+
metadata.ui_run_state = run.state;
|
|
3333
|
+
metadata.ui_run_requested_at = run.requestedAt;
|
|
3334
|
+
if (run.startedAt) {
|
|
3335
|
+
metadata.ui_run_started_at = run.startedAt;
|
|
3336
|
+
}
|
|
3337
|
+
} else if (metadata.ui_active_run_id === run.runId) {
|
|
3338
|
+
delete metadata.ui_active_run_id;
|
|
3339
|
+
delete metadata.ui_run_state;
|
|
3340
|
+
delete metadata.ui_run_requested_at;
|
|
3341
|
+
delete metadata.ui_run_started_at;
|
|
3342
|
+
}
|
|
3343
|
+
session.updatedAt = /* @__PURE__ */ new Date();
|
|
3344
|
+
this.options.sessionManager.save(session);
|
|
3345
|
+
}
|
|
3346
|
+
persistAbortedAssistantReply(sessionKey, partialReply) {
|
|
3347
|
+
const session = this.options.sessionManager.getOrCreate(sessionKey);
|
|
3348
|
+
const latest = session.messages.length > 0 ? session.messages[session.messages.length - 1] : null;
|
|
3349
|
+
if (latest?.role === "assistant" && typeof latest.content === "string" && latest.content === partialReply) {
|
|
3350
|
+
return;
|
|
3351
|
+
}
|
|
3352
|
+
this.options.sessionManager.addMessage(session, "assistant", partialReply);
|
|
3353
|
+
this.options.sessionManager.save(session);
|
|
3354
|
+
}
|
|
3355
|
+
mapSessionEvent(event) {
|
|
3356
|
+
const raw = event.data?.message;
|
|
3357
|
+
const messageRecord = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
|
|
3358
|
+
const message = messageRecord && typeof messageRecord.role === "string" ? {
|
|
3359
|
+
role: messageRecord.role,
|
|
3360
|
+
content: messageRecord.content,
|
|
3361
|
+
timestamp: typeof messageRecord.timestamp === "string" ? messageRecord.timestamp : event.timestamp,
|
|
3362
|
+
...typeof messageRecord.name === "string" ? { name: messageRecord.name } : {},
|
|
3363
|
+
...typeof messageRecord.tool_call_id === "string" ? { tool_call_id: messageRecord.tool_call_id } : {},
|
|
3364
|
+
...Array.isArray(messageRecord.tool_calls) ? { tool_calls: messageRecord.tool_calls } : {},
|
|
3365
|
+
...typeof messageRecord.reasoning_content === "string" ? { reasoning_content: messageRecord.reasoning_content } : {}
|
|
3366
|
+
} : void 0;
|
|
3367
|
+
return {
|
|
3368
|
+
seq: event.seq,
|
|
3369
|
+
type: event.type,
|
|
3370
|
+
timestamp: event.timestamp,
|
|
3371
|
+
...message ? { message } : {}
|
|
3372
|
+
};
|
|
3373
|
+
}
|
|
3374
|
+
emitRunUpdated(run) {
|
|
3375
|
+
this.options.onRunUpdated?.(this.toRunView(run));
|
|
3376
|
+
}
|
|
3377
|
+
toRunView(run) {
|
|
3378
|
+
return {
|
|
3379
|
+
runId: run.runId,
|
|
3380
|
+
sessionKey: run.sessionKey,
|
|
3381
|
+
...run.agentId ? { agentId: run.agentId } : {},
|
|
3382
|
+
...run.model ? { model: run.model } : {},
|
|
3383
|
+
state: run.state,
|
|
3384
|
+
requestedAt: run.requestedAt,
|
|
3385
|
+
...run.startedAt ? { startedAt: run.startedAt } : {},
|
|
3386
|
+
...run.completedAt ? { completedAt: run.completedAt } : {},
|
|
3387
|
+
stopSupported: run.stopSupported,
|
|
3388
|
+
...run.stopReason ? { stopReason: run.stopReason } : {},
|
|
3389
|
+
...run.error ? { error: run.error } : {},
|
|
3390
|
+
...typeof run.reply === "string" ? { reply: run.reply } : {},
|
|
3391
|
+
eventCount: run.events.length
|
|
3392
|
+
};
|
|
3393
|
+
}
|
|
3394
|
+
getRunPath(runId) {
|
|
3395
|
+
return join4(RUNS_DIR, `${safeFilename(runId)}.json`);
|
|
3396
|
+
}
|
|
3397
|
+
persistRun(run) {
|
|
3398
|
+
const persisted = {
|
|
3399
|
+
runId: run.runId,
|
|
3400
|
+
sessionKey: run.sessionKey,
|
|
3401
|
+
...run.agentId ? { agentId: run.agentId } : {},
|
|
3402
|
+
...run.model ? { model: run.model } : {},
|
|
3403
|
+
state: run.state,
|
|
3404
|
+
requestedAt: run.requestedAt,
|
|
3405
|
+
...run.startedAt ? { startedAt: run.startedAt } : {},
|
|
3406
|
+
...run.completedAt ? { completedAt: run.completedAt } : {},
|
|
3407
|
+
stopSupported: run.stopSupported,
|
|
3408
|
+
...run.stopReason ? { stopReason: run.stopReason } : {},
|
|
3409
|
+
...run.error ? { error: run.error } : {},
|
|
3410
|
+
...typeof run.reply === "string" ? { reply: run.reply } : {},
|
|
3411
|
+
events: run.events
|
|
3412
|
+
};
|
|
3413
|
+
writeFileSync3(this.getRunPath(run.runId), `${JSON.stringify(persisted, null, 2)}
|
|
3414
|
+
`);
|
|
3415
|
+
}
|
|
3416
|
+
loadPersistedRuns() {
|
|
3417
|
+
if (!existsSync7(RUNS_DIR)) {
|
|
3418
|
+
return;
|
|
3419
|
+
}
|
|
3420
|
+
for (const entry of readdirSync(RUNS_DIR, { withFileTypes: true })) {
|
|
3421
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
3422
|
+
continue;
|
|
3423
|
+
}
|
|
3424
|
+
const path = join4(RUNS_DIR, entry.name);
|
|
3425
|
+
try {
|
|
3426
|
+
const parsed = JSON.parse(readFileSync6(path, "utf-8"));
|
|
3427
|
+
const runId = readOptionalString(parsed.runId);
|
|
3428
|
+
const sessionKey = readOptionalString(parsed.sessionKey);
|
|
3429
|
+
if (!runId || !sessionKey) {
|
|
3430
|
+
continue;
|
|
3431
|
+
}
|
|
3432
|
+
const state = this.normalizeRunState(parsed.state);
|
|
3433
|
+
const events = Array.isArray(parsed.events) ? parsed.events : [];
|
|
3434
|
+
const run = {
|
|
3435
|
+
runId,
|
|
3436
|
+
sessionKey,
|
|
3437
|
+
...readOptionalString(parsed.agentId) ? { agentId: readOptionalString(parsed.agentId) } : {},
|
|
3438
|
+
...readOptionalString(parsed.model) ? { model: readOptionalString(parsed.model) } : {},
|
|
3439
|
+
state,
|
|
3440
|
+
requestedAt: readOptionalString(parsed.requestedAt) ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
3441
|
+
...readOptionalString(parsed.startedAt) ? { startedAt: readOptionalString(parsed.startedAt) } : {},
|
|
3442
|
+
...readOptionalString(parsed.completedAt) ? { completedAt: readOptionalString(parsed.completedAt) } : {},
|
|
3443
|
+
stopSupported: Boolean(parsed.stopSupported),
|
|
3444
|
+
...readOptionalString(parsed.stopReason) ? { stopReason: readOptionalString(parsed.stopReason) } : {},
|
|
3445
|
+
...readOptionalString(parsed.error) ? { error: readOptionalString(parsed.error) } : {},
|
|
3446
|
+
...typeof parsed.reply === "string" ? { reply: parsed.reply } : {},
|
|
3447
|
+
events,
|
|
3448
|
+
waiters: /* @__PURE__ */ new Set(),
|
|
3449
|
+
abortController: null,
|
|
3450
|
+
cancelRequested: false
|
|
3451
|
+
};
|
|
3452
|
+
if (NON_TERMINAL_STATES.has(run.state)) {
|
|
3453
|
+
run.state = "failed";
|
|
3454
|
+
run.error = run.error ?? "run interrupted by service restart";
|
|
3455
|
+
run.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3456
|
+
this.persistRun(run);
|
|
3457
|
+
}
|
|
3458
|
+
this.runs.set(run.runId, run);
|
|
3459
|
+
this.bindRunToSession(run.sessionKey, run.runId);
|
|
3460
|
+
} catch {
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
normalizeRunState(value) {
|
|
3465
|
+
if (value === "queued" || value === "running" || value === "completed" || value === "failed" || value === "aborted") {
|
|
3466
|
+
return value;
|
|
3467
|
+
}
|
|
3468
|
+
return "failed";
|
|
3469
|
+
}
|
|
3470
|
+
getRunRecord(runId) {
|
|
3471
|
+
const normalized = readOptionalString(runId);
|
|
3472
|
+
if (!normalized) {
|
|
3473
|
+
return null;
|
|
3474
|
+
}
|
|
3475
|
+
return this.runs.get(normalized) ?? null;
|
|
3476
|
+
}
|
|
3477
|
+
};
|
|
3478
|
+
|
|
2983
3479
|
// src/cli/commands/service.ts
|
|
2984
3480
|
var {
|
|
2985
3481
|
APP_NAME: APP_NAME2,
|
|
@@ -2987,7 +3483,7 @@ var {
|
|
|
2987
3483
|
CronService: CronService2,
|
|
2988
3484
|
getApiBase,
|
|
2989
3485
|
getConfigPath: getConfigPath3,
|
|
2990
|
-
getDataDir:
|
|
3486
|
+
getDataDir: getDataDir6,
|
|
2991
3487
|
getProvider,
|
|
2992
3488
|
getProviderName,
|
|
2993
3489
|
getWorkspacePath: getWorkspacePath5,
|
|
@@ -2999,7 +3495,7 @@ var {
|
|
|
2999
3495
|
resolveConfigSecrets: resolveConfigSecrets2,
|
|
3000
3496
|
saveConfig: saveConfig5,
|
|
3001
3497
|
SessionManager,
|
|
3002
|
-
parseAgentScopedSessionKey:
|
|
3498
|
+
parseAgentScopedSessionKey: parseAgentScopedSessionKey3
|
|
3003
3499
|
} = NextclawCore;
|
|
3004
3500
|
function createSkillsLoader(workspace) {
|
|
3005
3501
|
const ctor = NextclawCore.SkillsLoader;
|
|
@@ -3008,21 +3504,6 @@ function createSkillsLoader(workspace) {
|
|
|
3008
3504
|
}
|
|
3009
3505
|
return new ctor(workspace);
|
|
3010
3506
|
}
|
|
3011
|
-
function isAbortError(error) {
|
|
3012
|
-
if (error instanceof DOMException && error.name === "AbortError") {
|
|
3013
|
-
return true;
|
|
3014
|
-
}
|
|
3015
|
-
if (error instanceof Error) {
|
|
3016
|
-
if (error.name === "AbortError") {
|
|
3017
|
-
return true;
|
|
3018
|
-
}
|
|
3019
|
-
const message = error.message.toLowerCase();
|
|
3020
|
-
if (message.includes("aborted") || message.includes("abort")) {
|
|
3021
|
-
return true;
|
|
3022
|
-
}
|
|
3023
|
-
}
|
|
3024
|
-
return false;
|
|
3025
|
-
}
|
|
3026
3507
|
var ServiceCommands = class {
|
|
3027
3508
|
constructor(deps) {
|
|
3028
3509
|
this.deps = deps;
|
|
@@ -3059,7 +3540,7 @@ var ServiceCommands = class {
|
|
|
3059
3540
|
}
|
|
3060
3541
|
}
|
|
3061
3542
|
};
|
|
3062
|
-
const cronStorePath =
|
|
3543
|
+
const cronStorePath = join5(getDataDir6(), "cron", "jobs.json");
|
|
3063
3544
|
const cron2 = new CronService2(cronStorePath);
|
|
3064
3545
|
const uiConfig = resolveUiConfig(config2, options.uiOverrides);
|
|
3065
3546
|
const uiStaticDir = options.uiStaticDir === void 0 ? resolveUiStaticDir() : options.uiStaticDir;
|
|
@@ -3213,7 +3694,7 @@ var ServiceCommands = class {
|
|
|
3213
3694
|
} else {
|
|
3214
3695
|
console.log("Warning: No channels enabled");
|
|
3215
3696
|
}
|
|
3216
|
-
this.startUiIfEnabled(uiConfig, uiStaticDir, cron2, runtimePool);
|
|
3697
|
+
this.startUiIfEnabled(uiConfig, uiStaticDir, cron2, runtimePool, sessionManager);
|
|
3217
3698
|
const cronStatus = cron2.status();
|
|
3218
3699
|
if (cronStatus.jobs > 0) {
|
|
3219
3700
|
console.log(`\u2713 Cron: ${cronStatus.jobs} scheduled jobs`);
|
|
@@ -3327,7 +3808,7 @@ var ServiceCommands = class {
|
|
|
3327
3808
|
}
|
|
3328
3809
|
const sessionKey = sentinelSessionKey ?? fallbackSessionKey ?? "cli:default";
|
|
3329
3810
|
const parsedSession = parseSessionKey(sessionKey);
|
|
3330
|
-
const parsedAgentSession =
|
|
3811
|
+
const parsedAgentSession = parseAgentScopedSessionKey3(sessionKey);
|
|
3331
3812
|
const parsedSessionRoute = parsedSession && parsedSession.channel !== "agent" ? parsedSession : null;
|
|
3332
3813
|
const context = payload.deliveryContext;
|
|
3333
3814
|
const channel = this.normalizeOptionalString(context?.channel) ?? parsedSessionRoute?.channel ?? this.normalizeOptionalString((params.sessionManager.getIfExists(sessionKey)?.metadata ?? {}).last_channel);
|
|
@@ -3426,50 +3907,65 @@ var ServiceCommands = class {
|
|
|
3426
3907
|
}
|
|
3427
3908
|
const logPath = resolveServiceLogPath();
|
|
3428
3909
|
const logDir = resolve7(logPath, "..");
|
|
3429
|
-
|
|
3910
|
+
mkdirSync4(logDir, { recursive: true });
|
|
3430
3911
|
const logFd = openSync(logPath, "a");
|
|
3912
|
+
const readinessTimeoutMs = this.resolveStartupTimeoutMs(options.startupTimeoutMs);
|
|
3913
|
+
const quickPhaseTimeoutMs = Math.min(8e3, readinessTimeoutMs);
|
|
3914
|
+
const extendedPhaseTimeoutMs = Math.max(0, readinessTimeoutMs - quickPhaseTimeoutMs);
|
|
3915
|
+
this.appendStartupStage(
|
|
3916
|
+
logPath,
|
|
3917
|
+
`start requested: ui=${uiConfig.host}:${uiConfig.port}, readinessTimeoutMs=${readinessTimeoutMs}`
|
|
3918
|
+
);
|
|
3919
|
+
console.log(`Starting ${APP_NAME2} background service (readiness timeout ${Math.ceil(readinessTimeoutMs / 1e3)}s)...`);
|
|
3431
3920
|
const serveArgs = buildServeArgs({
|
|
3432
3921
|
uiPort: uiConfig.port
|
|
3433
3922
|
});
|
|
3923
|
+
this.appendStartupStage(logPath, `spawning background process: ${process.execPath} ${[...process.execArgv, ...serveArgs].join(" ")}`);
|
|
3434
3924
|
const child = spawn2(process.execPath, [...process.execArgv, ...serveArgs], {
|
|
3435
3925
|
env: process.env,
|
|
3436
3926
|
stdio: ["ignore", logFd, logFd],
|
|
3437
3927
|
detached: true
|
|
3438
3928
|
});
|
|
3929
|
+
this.appendStartupStage(logPath, `spawned background process pid=${child.pid ?? "unknown"}`);
|
|
3439
3930
|
closeSync(logFd);
|
|
3440
3931
|
if (!child.pid) {
|
|
3932
|
+
this.appendStartupStage(logPath, "spawn failed: child pid missing");
|
|
3441
3933
|
console.error("Error: Failed to start background service.");
|
|
3442
3934
|
return;
|
|
3443
3935
|
}
|
|
3444
3936
|
const healthUrl = `${apiUrl}/health`;
|
|
3937
|
+
this.appendStartupStage(logPath, `health probe started: ${healthUrl} (phase=quick, timeoutMs=${quickPhaseTimeoutMs})`);
|
|
3445
3938
|
let readiness = await this.waitForBackgroundServiceReady({
|
|
3446
3939
|
pid: child.pid,
|
|
3447
3940
|
healthUrl,
|
|
3448
|
-
timeoutMs:
|
|
3941
|
+
timeoutMs: quickPhaseTimeoutMs
|
|
3449
3942
|
});
|
|
3450
|
-
if (!readiness.ready && isProcessRunning(child.pid)) {
|
|
3451
|
-
const extendedTimeoutMs = process.platform === "win32" ? 2e4 : 25e3;
|
|
3943
|
+
if (!readiness.ready && isProcessRunning(child.pid) && extendedPhaseTimeoutMs > 0) {
|
|
3452
3944
|
console.warn(
|
|
3453
|
-
`Warning: Background service is still running but not ready after
|
|
3945
|
+
`Warning: Background service is still running but not ready after ${Math.ceil(quickPhaseTimeoutMs / 1e3)}s; waiting up to ${Math.ceil(extendedPhaseTimeoutMs / 1e3)}s more.`
|
|
3946
|
+
);
|
|
3947
|
+
this.appendStartupStage(
|
|
3948
|
+
logPath,
|
|
3949
|
+
`health probe entering extended phase (timeoutMs=${extendedPhaseTimeoutMs}, lastError=${readiness.lastProbeError ?? "none"})`
|
|
3454
3950
|
);
|
|
3455
3951
|
readiness = await this.waitForBackgroundServiceReady({
|
|
3456
3952
|
pid: child.pid,
|
|
3457
3953
|
healthUrl,
|
|
3458
|
-
timeoutMs:
|
|
3954
|
+
timeoutMs: extendedPhaseTimeoutMs
|
|
3459
3955
|
});
|
|
3460
3956
|
}
|
|
3461
3957
|
if (!readiness.ready) {
|
|
3462
|
-
if (isProcessRunning(child.pid)) {
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3958
|
+
if (!isProcessRunning(child.pid)) {
|
|
3959
|
+
clearServiceState();
|
|
3960
|
+
const hint = readiness.lastProbeError ? ` Last probe error: ${readiness.lastProbeError}` : "";
|
|
3961
|
+
this.appendStartupStage(logPath, `startup failed: process exited before ready.${hint}`);
|
|
3962
|
+
console.error(`Error: Failed to start background service. Check logs: ${logPath}.${hint}`);
|
|
3963
|
+
return;
|
|
3468
3964
|
}
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3965
|
+
this.appendStartupStage(
|
|
3966
|
+
logPath,
|
|
3967
|
+
`startup degraded: process alive but health probe timed out after ${readinessTimeoutMs}ms (lastError=${readiness.lastProbeError ?? "none"})`
|
|
3968
|
+
);
|
|
3473
3969
|
}
|
|
3474
3970
|
child.unref();
|
|
3475
3971
|
const state = {
|
|
@@ -3479,10 +3975,22 @@ var ServiceCommands = class {
|
|
|
3479
3975
|
apiUrl,
|
|
3480
3976
|
uiHost: uiConfig.host,
|
|
3481
3977
|
uiPort: uiConfig.port,
|
|
3482
|
-
logPath
|
|
3978
|
+
logPath,
|
|
3979
|
+
startupState: readiness.ready ? "ready" : "degraded",
|
|
3980
|
+
startupLastProbeError: readiness.lastProbeError,
|
|
3981
|
+
startupTimeoutMs: readinessTimeoutMs,
|
|
3982
|
+
startupCheckedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3483
3983
|
};
|
|
3484
3984
|
writeServiceState(state);
|
|
3485
|
-
|
|
3985
|
+
if (!readiness.ready) {
|
|
3986
|
+
const hint = readiness.lastProbeError ? ` Last probe error: ${readiness.lastProbeError}` : "";
|
|
3987
|
+
console.warn(
|
|
3988
|
+
`Warning: ${APP_NAME2} is running (PID ${state.pid}) but not healthy yet after ${Math.ceil(readinessTimeoutMs / 1e3)}s. Marked as degraded.${hint}`
|
|
3989
|
+
);
|
|
3990
|
+
console.warn(`Tip: Run "${APP_NAME2} status --json" and check logs: ${logPath}`);
|
|
3991
|
+
} else {
|
|
3992
|
+
console.log(`\u2713 ${APP_NAME2} started in background (PID ${state.pid})`);
|
|
3993
|
+
}
|
|
3486
3994
|
console.log(`UI: ${uiUrl}`);
|
|
3487
3995
|
console.log(`API: ${apiUrl}`);
|
|
3488
3996
|
await this.printPublicUiUrls(uiConfig.host, uiConfig.port);
|
|
@@ -3544,6 +4052,22 @@ var ServiceCommands = class {
|
|
|
3544
4052
|
}
|
|
3545
4053
|
return { ready: false, lastProbeError };
|
|
3546
4054
|
}
|
|
4055
|
+
resolveStartupTimeoutMs(overrideTimeoutMs) {
|
|
4056
|
+
const fallback = process.platform === "win32" ? 28e3 : 33e3;
|
|
4057
|
+
const envRaw = process.env.NEXTCLAW_START_TIMEOUT_MS?.trim();
|
|
4058
|
+
const envValue = envRaw ? Number(envRaw) : Number.NaN;
|
|
4059
|
+
const fromEnv = Number.isFinite(envValue) && envValue > 0 ? Math.floor(envValue) : null;
|
|
4060
|
+
const fromOverride = Number.isFinite(overrideTimeoutMs) && Number(overrideTimeoutMs) > 0 ? Math.floor(Number(overrideTimeoutMs)) : null;
|
|
4061
|
+
const resolved = fromOverride ?? fromEnv ?? fallback;
|
|
4062
|
+
return Math.max(3e3, resolved);
|
|
4063
|
+
}
|
|
4064
|
+
appendStartupStage(logPath, message) {
|
|
4065
|
+
try {
|
|
4066
|
+
appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] [startup] ${message}
|
|
4067
|
+
`, "utf-8");
|
|
4068
|
+
} catch {
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
3547
4071
|
async probeHealthEndpoint(healthUrl) {
|
|
3548
4072
|
let parsed;
|
|
3549
4073
|
try {
|
|
@@ -3651,11 +4175,10 @@ var ServiceCommands = class {
|
|
|
3651
4175
|
console.log(` - Check status: ${APP_NAME2} status`);
|
|
3652
4176
|
console.log(` - If you need to stop the service, run: ${APP_NAME2} stop`);
|
|
3653
4177
|
}
|
|
3654
|
-
startUiIfEnabled(uiConfig, uiStaticDir, cronService, runtimePool) {
|
|
4178
|
+
startUiIfEnabled(uiConfig, uiStaticDir, cronService, runtimePool, sessionManager) {
|
|
3655
4179
|
if (!uiConfig.enabled) {
|
|
3656
4180
|
return;
|
|
3657
4181
|
}
|
|
3658
|
-
const activeTurnRuns = /* @__PURE__ */ new Map();
|
|
3659
4182
|
const resolveStopCapability = (params) => runtimePool.supportsTurnAbort({
|
|
3660
4183
|
sessionKey: params.sessionKey,
|
|
3661
4184
|
agentId: params.agentId,
|
|
@@ -3665,7 +4188,7 @@ var ServiceCommands = class {
|
|
|
3665
4188
|
});
|
|
3666
4189
|
const resolveChatTurnParams = (params) => {
|
|
3667
4190
|
const sessionKey = typeof params.sessionKey === "string" && params.sessionKey.trim().length > 0 ? params.sessionKey.trim() : `ui:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 8)}`;
|
|
3668
|
-
const inferredAgentId = typeof params.agentId === "string" && params.agentId.trim().length > 0 ? params.agentId.trim() :
|
|
4191
|
+
const inferredAgentId = typeof params.agentId === "string" && params.agentId.trim().length > 0 ? params.agentId.trim() : parseAgentScopedSessionKey3(sessionKey)?.agentId;
|
|
3669
4192
|
const model = typeof params.model === "string" && params.model.trim().length > 0 ? params.model.trim() : void 0;
|
|
3670
4193
|
const metadata = params.metadata && typeof params.metadata === "object" && !Array.isArray(params.metadata) ? { ...params.metadata } : {};
|
|
3671
4194
|
if (model) {
|
|
@@ -3688,6 +4211,14 @@ var ServiceCommands = class {
|
|
|
3688
4211
|
...params.inferredAgentId ? { agentId: params.inferredAgentId } : {},
|
|
3689
4212
|
...params.model ? { model: params.model } : {}
|
|
3690
4213
|
});
|
|
4214
|
+
let publishUiEvent = null;
|
|
4215
|
+
const runCoordinator = new UiChatRunCoordinator({
|
|
4216
|
+
runtimePool,
|
|
4217
|
+
sessionManager,
|
|
4218
|
+
onRunUpdated: (run) => {
|
|
4219
|
+
publishUiEvent?.({ type: "run.updated", payload: { run } });
|
|
4220
|
+
}
|
|
4221
|
+
});
|
|
3691
4222
|
const uiServer = startUiServer({
|
|
3692
4223
|
host: uiConfig.host,
|
|
3693
4224
|
port: uiConfig.port,
|
|
@@ -3737,153 +4268,32 @@ var ServiceCommands = class {
|
|
|
3737
4268
|
model: resolved.model
|
|
3738
4269
|
});
|
|
3739
4270
|
},
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
stopped: false,
|
|
3753
|
-
runId,
|
|
3754
|
-
...typeof params.sessionKey === "string" && params.sessionKey.trim().length > 0 ? { sessionKey: params.sessionKey.trim() } : {},
|
|
3755
|
-
reason: "run not found or already completed"
|
|
3756
|
-
};
|
|
3757
|
-
}
|
|
3758
|
-
const requestedSessionKey = typeof params.sessionKey === "string" ? params.sessionKey.trim() : "";
|
|
3759
|
-
if (requestedSessionKey && requestedSessionKey !== active.sessionKey) {
|
|
3760
|
-
return {
|
|
3761
|
-
stopped: false,
|
|
3762
|
-
runId,
|
|
3763
|
-
sessionKey: active.sessionKey,
|
|
3764
|
-
reason: "session key mismatch"
|
|
3765
|
-
};
|
|
4271
|
+
startTurnRun: async (params) => {
|
|
4272
|
+
return runCoordinator.startRun(params);
|
|
4273
|
+
},
|
|
4274
|
+
listRuns: async (params) => {
|
|
4275
|
+
return runCoordinator.listRuns(params);
|
|
4276
|
+
},
|
|
4277
|
+
getRun: async (params) => {
|
|
4278
|
+
return runCoordinator.getRun(params);
|
|
4279
|
+
},
|
|
4280
|
+
streamRun: async function* (params) {
|
|
4281
|
+
for await (const event of runCoordinator.streamRun(params)) {
|
|
4282
|
+
yield event;
|
|
3766
4283
|
}
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
runId,
|
|
3771
|
-
sessionKey: active.sessionKey
|
|
3772
|
-
};
|
|
4284
|
+
},
|
|
4285
|
+
stopTurn: async (params) => {
|
|
4286
|
+
return await runCoordinator.stopRun(params);
|
|
3773
4287
|
},
|
|
3774
4288
|
processTurnStream: async function* (params) {
|
|
3775
|
-
const
|
|
3776
|
-
const
|
|
3777
|
-
|
|
3778
|
-
sessionKey: resolved.sessionKey,
|
|
3779
|
-
agentId: resolved.inferredAgentId,
|
|
3780
|
-
channel: resolved.channel,
|
|
3781
|
-
chatId: resolved.chatId,
|
|
3782
|
-
metadata: resolved.metadata
|
|
3783
|
-
});
|
|
3784
|
-
const controller = stopCapability.supported ? new AbortController() : null;
|
|
3785
|
-
if (controller) {
|
|
3786
|
-
activeTurnRuns.set(runId, {
|
|
3787
|
-
controller,
|
|
3788
|
-
sessionKey: resolved.sessionKey,
|
|
3789
|
-
...resolved.inferredAgentId ? { agentId: resolved.inferredAgentId } : {}
|
|
3790
|
-
});
|
|
3791
|
-
}
|
|
3792
|
-
const queue = [];
|
|
3793
|
-
const assistantDeltaParts = [];
|
|
3794
|
-
let waiter = null;
|
|
3795
|
-
const push = (event) => {
|
|
3796
|
-
queue.push(event);
|
|
3797
|
-
const currentWaiter = waiter;
|
|
3798
|
-
waiter = null;
|
|
3799
|
-
currentWaiter?.();
|
|
3800
|
-
};
|
|
3801
|
-
const run = runtimePool.processDirect({
|
|
3802
|
-
content: params.message,
|
|
3803
|
-
sessionKey: resolved.sessionKey,
|
|
3804
|
-
channel: resolved.channel,
|
|
3805
|
-
chatId: resolved.chatId,
|
|
3806
|
-
agentId: resolved.inferredAgentId,
|
|
3807
|
-
metadata: resolved.metadata,
|
|
3808
|
-
...controller ? { abortSignal: controller.signal } : {},
|
|
3809
|
-
onAssistantDelta: (delta) => {
|
|
3810
|
-
if (typeof delta !== "string" || delta.length === 0) {
|
|
3811
|
-
return;
|
|
3812
|
-
}
|
|
3813
|
-
assistantDeltaParts.push(delta);
|
|
3814
|
-
push({ type: "delta", delta });
|
|
3815
|
-
},
|
|
3816
|
-
onSessionEvent: (event) => {
|
|
3817
|
-
const raw = event.data?.message;
|
|
3818
|
-
const messageRecord = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
|
|
3819
|
-
const message = messageRecord && typeof messageRecord.role === "string" ? {
|
|
3820
|
-
role: messageRecord.role,
|
|
3821
|
-
content: messageRecord.content,
|
|
3822
|
-
timestamp: typeof messageRecord.timestamp === "string" ? messageRecord.timestamp : event.timestamp,
|
|
3823
|
-
...typeof messageRecord.name === "string" ? { name: messageRecord.name } : {},
|
|
3824
|
-
...typeof messageRecord.tool_call_id === "string" ? { tool_call_id: messageRecord.tool_call_id } : {},
|
|
3825
|
-
...Array.isArray(messageRecord.tool_calls) ? { tool_calls: messageRecord.tool_calls } : {},
|
|
3826
|
-
...typeof messageRecord.reasoning_content === "string" ? { reasoning_content: messageRecord.reasoning_content } : {}
|
|
3827
|
-
} : void 0;
|
|
3828
|
-
push({
|
|
3829
|
-
type: "session_event",
|
|
3830
|
-
event: {
|
|
3831
|
-
seq: event.seq,
|
|
3832
|
-
type: event.type,
|
|
3833
|
-
timestamp: event.timestamp,
|
|
3834
|
-
...message ? { message } : {}
|
|
3835
|
-
}
|
|
3836
|
-
});
|
|
3837
|
-
}
|
|
3838
|
-
}).then((reply) => {
|
|
3839
|
-
push({
|
|
3840
|
-
type: "final",
|
|
3841
|
-
result: buildTurnResult({
|
|
3842
|
-
reply,
|
|
3843
|
-
sessionKey: resolved.sessionKey,
|
|
3844
|
-
inferredAgentId: resolved.inferredAgentId,
|
|
3845
|
-
model: resolved.model
|
|
3846
|
-
})
|
|
3847
|
-
});
|
|
3848
|
-
}).catch((error) => {
|
|
3849
|
-
if ((controller?.signal.aborted ?? false) || isAbortError(error)) {
|
|
3850
|
-
const partialReply = assistantDeltaParts.join("");
|
|
3851
|
-
push({
|
|
3852
|
-
type: "final",
|
|
3853
|
-
result: buildTurnResult({
|
|
3854
|
-
reply: partialReply,
|
|
3855
|
-
sessionKey: resolved.sessionKey,
|
|
3856
|
-
inferredAgentId: resolved.inferredAgentId,
|
|
3857
|
-
model: resolved.model
|
|
3858
|
-
})
|
|
3859
|
-
});
|
|
3860
|
-
return;
|
|
3861
|
-
}
|
|
3862
|
-
push({ type: "error", error: String(error) });
|
|
3863
|
-
}).finally(() => {
|
|
3864
|
-
activeTurnRuns.delete(runId);
|
|
3865
|
-
});
|
|
3866
|
-
while (true) {
|
|
3867
|
-
if (queue.length === 0) {
|
|
3868
|
-
await new Promise((resolve10) => {
|
|
3869
|
-
waiter = resolve10;
|
|
3870
|
-
});
|
|
3871
|
-
}
|
|
3872
|
-
while (queue.length > 0) {
|
|
3873
|
-
const event = queue.shift();
|
|
3874
|
-
if (!event) {
|
|
3875
|
-
continue;
|
|
3876
|
-
}
|
|
3877
|
-
yield event;
|
|
3878
|
-
if (event.type === "final" || event.type === "error") {
|
|
3879
|
-
await run;
|
|
3880
|
-
return;
|
|
3881
|
-
}
|
|
3882
|
-
}
|
|
4289
|
+
const run = runCoordinator.startRun(params);
|
|
4290
|
+
for await (const event of runCoordinator.streamRun({ runId: run.runId })) {
|
|
4291
|
+
yield event;
|
|
3883
4292
|
}
|
|
3884
4293
|
}
|
|
3885
4294
|
}
|
|
3886
4295
|
});
|
|
4296
|
+
publishUiEvent = uiServer.publish;
|
|
3887
4297
|
const uiUrl = `http://${uiServer.host}:${uiServer.port}`;
|
|
3888
4298
|
console.log(`\u2713 UI API: ${uiUrl}/api`);
|
|
3889
4299
|
if (uiStaticDir) {
|
|
@@ -3940,17 +4350,17 @@ var ServiceCommands = class {
|
|
|
3940
4350
|
const workspace = getWorkspacePath5(loadConfig6().agents.defaults.workspace);
|
|
3941
4351
|
const skillName = this.resolveGitSkillName(params.skill, source);
|
|
3942
4352
|
const destination = this.resolveSkillInstallPath(workspace, params.installPath, skillName);
|
|
3943
|
-
const destinationSkillFile =
|
|
3944
|
-
if (
|
|
4353
|
+
const destinationSkillFile = join5(destination, "SKILL.md");
|
|
4354
|
+
if (existsSync8(destinationSkillFile) && !params.force) {
|
|
3945
4355
|
return {
|
|
3946
4356
|
message: `${skillName} is already installed`,
|
|
3947
4357
|
output: destination
|
|
3948
4358
|
};
|
|
3949
4359
|
}
|
|
3950
|
-
if (
|
|
4360
|
+
if (existsSync8(destination) && !params.force) {
|
|
3951
4361
|
throw new Error(`Skill install path already exists: ${destination} (use force to overwrite)`);
|
|
3952
4362
|
}
|
|
3953
|
-
if (
|
|
4363
|
+
if (existsSync8(destination) && params.force) {
|
|
3954
4364
|
rmSync3(destination, { recursive: true, force: true });
|
|
3955
4365
|
}
|
|
3956
4366
|
const skildArgs = ["--yes", "skild", "install", source, "--target", "agents", "--local", "--json", "--skill", skillName];
|
|
@@ -3985,11 +4395,11 @@ var ServiceCommands = class {
|
|
|
3985
4395
|
throw new Error("skild returned null json payload even after force reinstall");
|
|
3986
4396
|
}
|
|
3987
4397
|
const installDir = typeof payload.installDir === "string" ? payload.installDir.trim() : "";
|
|
3988
|
-
const installSkillFile = installDir ?
|
|
3989
|
-
if (!installDir || !
|
|
4398
|
+
const installSkillFile = installDir ? join5(installDir, "SKILL.md") : "";
|
|
4399
|
+
if (!installDir || !existsSync8(installSkillFile)) {
|
|
3990
4400
|
throw new Error(`skild install did not produce a valid skill directory for ${skillName}`);
|
|
3991
4401
|
}
|
|
3992
|
-
|
|
4402
|
+
mkdirSync4(dirname(destination), { recursive: true });
|
|
3993
4403
|
if (resolve7(installDir) !== resolve7(destination)) {
|
|
3994
4404
|
cpSync(installDir, destination, { recursive: true, force: true });
|
|
3995
4405
|
}
|
|
@@ -4020,9 +4430,9 @@ var ServiceCommands = class {
|
|
|
4020
4430
|
}
|
|
4021
4431
|
async uninstallMarketplaceSkill(slug) {
|
|
4022
4432
|
const workspace = getWorkspacePath5(loadConfig6().agents.defaults.workspace);
|
|
4023
|
-
const targetDir =
|
|
4024
|
-
const skildDir =
|
|
4025
|
-
const existingTargets = [targetDir, skildDir].filter((path) =>
|
|
4433
|
+
const targetDir = join5(workspace, "skills", slug);
|
|
4434
|
+
const skildDir = join5(workspace, ".agents", "skills", slug);
|
|
4435
|
+
const existingTargets = [targetDir, skildDir].filter((path) => existsSync8(path));
|
|
4026
4436
|
if (existingTargets.length === 0) {
|
|
4027
4437
|
throw new Error(`Skill not installed in workspace: ${slug}`);
|
|
4028
4438
|
}
|
|
@@ -4036,9 +4446,9 @@ var ServiceCommands = class {
|
|
|
4036
4446
|
}
|
|
4037
4447
|
installBuiltinMarketplaceSkill(slug, force) {
|
|
4038
4448
|
const workspace = getWorkspacePath5(loadConfig6().agents.defaults.workspace);
|
|
4039
|
-
const destination =
|
|
4040
|
-
const destinationSkillFile =
|
|
4041
|
-
if (
|
|
4449
|
+
const destination = join5(workspace, "skills", slug);
|
|
4450
|
+
const destinationSkillFile = join5(destination, "SKILL.md");
|
|
4451
|
+
if (existsSync8(destinationSkillFile) && !force) {
|
|
4042
4452
|
return {
|
|
4043
4453
|
message: `${slug} is already installed`,
|
|
4044
4454
|
output: destination
|
|
@@ -4047,7 +4457,7 @@ var ServiceCommands = class {
|
|
|
4047
4457
|
const loader = createSkillsLoader(workspace);
|
|
4048
4458
|
const builtin = (loader?.listSkills(false) ?? []).find((skill) => skill.name === slug && skill.source === "builtin");
|
|
4049
4459
|
if (!builtin) {
|
|
4050
|
-
if (
|
|
4460
|
+
if (existsSync8(destinationSkillFile)) {
|
|
4051
4461
|
return {
|
|
4052
4462
|
message: `${slug} is already installed`,
|
|
4053
4463
|
output: destination
|
|
@@ -4055,7 +4465,7 @@ var ServiceCommands = class {
|
|
|
4055
4465
|
}
|
|
4056
4466
|
return null;
|
|
4057
4467
|
}
|
|
4058
|
-
|
|
4468
|
+
mkdirSync4(join5(workspace, "skills"), { recursive: true });
|
|
4059
4469
|
cpSync(dirname(builtin.path), destination, { recursive: true, force: true });
|
|
4060
4470
|
return {
|
|
4061
4471
|
message: `Installed skill: ${slug}`,
|
|
@@ -4082,7 +4492,7 @@ var ServiceCommands = class {
|
|
|
4082
4492
|
return skillName;
|
|
4083
4493
|
}
|
|
4084
4494
|
resolveSkillInstallPath(workspace, installPath, skillName) {
|
|
4085
|
-
const requested = typeof installPath === "string" && installPath.trim().length > 0 ? installPath.trim() :
|
|
4495
|
+
const requested = typeof installPath === "string" && installPath.trim().length > 0 ? installPath.trim() : join5("skills", skillName);
|
|
4086
4496
|
if (isAbsolute2(requested)) {
|
|
4087
4497
|
throw new Error("installPath must be relative to workspace");
|
|
4088
4498
|
}
|
|
@@ -4197,11 +4607,11 @@ ${stderr}`.trim();
|
|
|
4197
4607
|
};
|
|
4198
4608
|
|
|
4199
4609
|
// src/cli/workspace.ts
|
|
4200
|
-
import { cpSync as cpSync2, existsSync as
|
|
4610
|
+
import { cpSync as cpSync2, existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync7, readdirSync as readdirSync2, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
4201
4611
|
import { createRequire } from "module";
|
|
4202
|
-
import { dirname as dirname2, join as
|
|
4612
|
+
import { dirname as dirname2, join as join6, resolve as resolve8 } from "path";
|
|
4203
4613
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4204
|
-
import { APP_NAME as APP_NAME3, getDataDir as
|
|
4614
|
+
import { APP_NAME as APP_NAME3, getDataDir as getDataDir7 } from "@nextclaw/core";
|
|
4205
4615
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
4206
4616
|
var WorkspaceManager = class {
|
|
4207
4617
|
constructor(logo) {
|
|
@@ -4229,30 +4639,30 @@ var WorkspaceManager = class {
|
|
|
4229
4639
|
{ source: "memory/MEMORY.md", target: "memory/MEMORY.md" }
|
|
4230
4640
|
];
|
|
4231
4641
|
for (const entry of templateFiles) {
|
|
4232
|
-
const filePath =
|
|
4233
|
-
if (!force &&
|
|
4642
|
+
const filePath = join6(workspace, entry.target);
|
|
4643
|
+
if (!force && existsSync9(filePath)) {
|
|
4234
4644
|
continue;
|
|
4235
4645
|
}
|
|
4236
|
-
const templatePath =
|
|
4237
|
-
if (!
|
|
4646
|
+
const templatePath = join6(templateDir, entry.source);
|
|
4647
|
+
if (!existsSync9(templatePath)) {
|
|
4238
4648
|
console.warn(`Warning: Template file missing: ${templatePath}`);
|
|
4239
4649
|
continue;
|
|
4240
4650
|
}
|
|
4241
|
-
const raw =
|
|
4651
|
+
const raw = readFileSync7(templatePath, "utf-8");
|
|
4242
4652
|
const content = raw.replace(/\$\{APP_NAME\}/g, APP_NAME3);
|
|
4243
|
-
|
|
4244
|
-
|
|
4653
|
+
mkdirSync5(dirname2(filePath), { recursive: true });
|
|
4654
|
+
writeFileSync4(filePath, content);
|
|
4245
4655
|
created.push(entry.target);
|
|
4246
4656
|
}
|
|
4247
|
-
const memoryDir =
|
|
4248
|
-
if (!
|
|
4249
|
-
|
|
4250
|
-
created.push(
|
|
4657
|
+
const memoryDir = join6(workspace, "memory");
|
|
4658
|
+
if (!existsSync9(memoryDir)) {
|
|
4659
|
+
mkdirSync5(memoryDir, { recursive: true });
|
|
4660
|
+
created.push(join6("memory", ""));
|
|
4251
4661
|
}
|
|
4252
|
-
const skillsDir =
|
|
4253
|
-
if (!
|
|
4254
|
-
|
|
4255
|
-
created.push(
|
|
4662
|
+
const skillsDir = join6(workspace, "skills");
|
|
4663
|
+
if (!existsSync9(skillsDir)) {
|
|
4664
|
+
mkdirSync5(skillsDir, { recursive: true });
|
|
4665
|
+
created.push(join6("skills", ""));
|
|
4256
4666
|
}
|
|
4257
4667
|
const seeded = this.seedBuiltinSkills(skillsDir, { force });
|
|
4258
4668
|
if (seeded > 0) {
|
|
@@ -4267,16 +4677,16 @@ var WorkspaceManager = class {
|
|
|
4267
4677
|
}
|
|
4268
4678
|
const force = Boolean(options.force);
|
|
4269
4679
|
let seeded = 0;
|
|
4270
|
-
for (const entry of
|
|
4680
|
+
for (const entry of readdirSync2(sourceDir, { withFileTypes: true })) {
|
|
4271
4681
|
if (!entry.isDirectory()) {
|
|
4272
4682
|
continue;
|
|
4273
4683
|
}
|
|
4274
|
-
const src =
|
|
4275
|
-
if (!
|
|
4684
|
+
const src = join6(sourceDir, entry.name);
|
|
4685
|
+
if (!existsSync9(join6(src, "SKILL.md"))) {
|
|
4276
4686
|
continue;
|
|
4277
4687
|
}
|
|
4278
|
-
const dest =
|
|
4279
|
-
if (!force &&
|
|
4688
|
+
const dest = join6(targetDir, entry.name);
|
|
4689
|
+
if (!force && existsSync9(dest)) {
|
|
4280
4690
|
continue;
|
|
4281
4691
|
}
|
|
4282
4692
|
cpSync2(src, dest, { recursive: true, force: true });
|
|
@@ -4289,12 +4699,12 @@ var WorkspaceManager = class {
|
|
|
4289
4699
|
const require2 = createRequire(import.meta.url);
|
|
4290
4700
|
const entry = require2.resolve("@nextclaw/core");
|
|
4291
4701
|
const pkgRoot = resolve8(dirname2(entry), "..");
|
|
4292
|
-
const distSkills =
|
|
4293
|
-
if (
|
|
4702
|
+
const distSkills = join6(pkgRoot, "dist", "skills");
|
|
4703
|
+
if (existsSync9(distSkills)) {
|
|
4294
4704
|
return distSkills;
|
|
4295
4705
|
}
|
|
4296
|
-
const srcSkills =
|
|
4297
|
-
if (
|
|
4706
|
+
const srcSkills = join6(pkgRoot, "src", "agent", "skills");
|
|
4707
|
+
if (existsSync9(srcSkills)) {
|
|
4298
4708
|
return srcSkills;
|
|
4299
4709
|
}
|
|
4300
4710
|
return null;
|
|
@@ -4309,17 +4719,17 @@ var WorkspaceManager = class {
|
|
|
4309
4719
|
}
|
|
4310
4720
|
const cliDir = resolve8(fileURLToPath3(new URL(".", import.meta.url)));
|
|
4311
4721
|
const pkgRoot = resolve8(cliDir, "..", "..");
|
|
4312
|
-
const candidates = [
|
|
4722
|
+
const candidates = [join6(pkgRoot, "templates")];
|
|
4313
4723
|
for (const candidate of candidates) {
|
|
4314
|
-
if (
|
|
4724
|
+
if (existsSync9(candidate)) {
|
|
4315
4725
|
return candidate;
|
|
4316
4726
|
}
|
|
4317
4727
|
}
|
|
4318
4728
|
return null;
|
|
4319
4729
|
}
|
|
4320
4730
|
getBridgeDir() {
|
|
4321
|
-
const userBridge =
|
|
4322
|
-
if (
|
|
4731
|
+
const userBridge = join6(getDataDir7(), "bridge");
|
|
4732
|
+
if (existsSync9(join6(userBridge, "dist", "index.js"))) {
|
|
4323
4733
|
return userBridge;
|
|
4324
4734
|
}
|
|
4325
4735
|
if (!which("npm")) {
|
|
@@ -4328,12 +4738,12 @@ var WorkspaceManager = class {
|
|
|
4328
4738
|
}
|
|
4329
4739
|
const cliDir = resolve8(fileURLToPath3(new URL(".", import.meta.url)));
|
|
4330
4740
|
const pkgRoot = resolve8(cliDir, "..", "..");
|
|
4331
|
-
const pkgBridge =
|
|
4332
|
-
const srcBridge =
|
|
4741
|
+
const pkgBridge = join6(pkgRoot, "bridge");
|
|
4742
|
+
const srcBridge = join6(pkgRoot, "..", "..", "bridge");
|
|
4333
4743
|
let source = null;
|
|
4334
|
-
if (
|
|
4744
|
+
if (existsSync9(join6(pkgBridge, "package.json"))) {
|
|
4335
4745
|
source = pkgBridge;
|
|
4336
|
-
} else if (
|
|
4746
|
+
} else if (existsSync9(join6(srcBridge, "package.json"))) {
|
|
4337
4747
|
source = srcBridge;
|
|
4338
4748
|
}
|
|
4339
4749
|
if (!source) {
|
|
@@ -4341,8 +4751,8 @@ var WorkspaceManager = class {
|
|
|
4341
4751
|
process.exit(1);
|
|
4342
4752
|
}
|
|
4343
4753
|
console.log(`${this.logo} Setting up bridge...`);
|
|
4344
|
-
|
|
4345
|
-
if (
|
|
4754
|
+
mkdirSync5(resolve8(userBridge, ".."), { recursive: true });
|
|
4755
|
+
if (existsSync9(userBridge)) {
|
|
4346
4756
|
rmSync4(userBridge, { recursive: true, force: true });
|
|
4347
4757
|
}
|
|
4348
4758
|
cpSync2(source, userBridge, {
|
|
@@ -4471,7 +4881,7 @@ var CliRuntime = class {
|
|
|
4471
4881
|
const delayMs = typeof params.delayMs === "number" && Number.isFinite(params.delayMs) ? Math.max(0, Math.floor(params.delayMs)) : 100;
|
|
4472
4882
|
const cliPath = process.env.NEXTCLAW_SELF_RELAUNCH_CLI?.trim() || fileURLToPath4(new URL("./index.js", import.meta.url));
|
|
4473
4883
|
const startArgs = [cliPath, "start", "--ui-port", String(uiPort)];
|
|
4474
|
-
const serviceStatePath = resolve9(
|
|
4884
|
+
const serviceStatePath = resolve9(getDataDir8(), "run", "service.json");
|
|
4475
4885
|
const helperScript = [
|
|
4476
4886
|
'const { spawnSync } = require("node:child_process");',
|
|
4477
4887
|
'const { readFileSync } = require("node:fs");',
|
|
@@ -4601,16 +5011,16 @@ var CliRuntime = class {
|
|
|
4601
5011
|
const force = Boolean(options.force);
|
|
4602
5012
|
const configPath = getConfigPath4();
|
|
4603
5013
|
let createdConfig = false;
|
|
4604
|
-
if (!
|
|
5014
|
+
if (!existsSync10(configPath)) {
|
|
4605
5015
|
const config3 = ConfigSchema2.parse({});
|
|
4606
5016
|
saveConfig6(config3);
|
|
4607
5017
|
createdConfig = true;
|
|
4608
5018
|
}
|
|
4609
5019
|
const config2 = loadConfig7();
|
|
4610
5020
|
const workspaceSetting = config2.agents.defaults.workspace;
|
|
4611
|
-
const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ?
|
|
4612
|
-
const workspaceExisted =
|
|
4613
|
-
|
|
5021
|
+
const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join7(getDataDir8(), DEFAULT_WORKSPACE_DIR) : expandHome2(workspaceSetting);
|
|
5022
|
+
const workspaceExisted = existsSync10(workspacePath);
|
|
5023
|
+
mkdirSync6(workspacePath, { recursive: true });
|
|
4614
5024
|
const templateResult = this.workspaceManager.createWorkspaceTemplates(
|
|
4615
5025
|
workspacePath,
|
|
4616
5026
|
{ force }
|
|
@@ -4669,6 +5079,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
4669
5079
|
});
|
|
4670
5080
|
}
|
|
4671
5081
|
async start(opts) {
|
|
5082
|
+
const startupTimeoutMs = this.parseStartTimeoutMs(opts.startTimeout);
|
|
4672
5083
|
await this.init({ source: "start", auto: true });
|
|
4673
5084
|
const uiOverrides = {
|
|
4674
5085
|
enabled: true,
|
|
@@ -4680,7 +5091,8 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
4680
5091
|
}
|
|
4681
5092
|
await this.serviceCommands.startService({
|
|
4682
5093
|
uiOverrides,
|
|
4683
|
-
open: Boolean(opts.open)
|
|
5094
|
+
open: Boolean(opts.open),
|
|
5095
|
+
startupTimeoutMs
|
|
4684
5096
|
});
|
|
4685
5097
|
}
|
|
4686
5098
|
async restart(opts) {
|
|
@@ -4711,6 +5123,17 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
4711
5123
|
open: Boolean(opts.open)
|
|
4712
5124
|
});
|
|
4713
5125
|
}
|
|
5126
|
+
parseStartTimeoutMs(value) {
|
|
5127
|
+
if (value === void 0) {
|
|
5128
|
+
return void 0;
|
|
5129
|
+
}
|
|
5130
|
+
const parsed = Number(value);
|
|
5131
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
5132
|
+
console.error("Invalid --start-timeout value. Provide milliseconds (e.g. 45000).");
|
|
5133
|
+
process.exit(1);
|
|
5134
|
+
}
|
|
5135
|
+
return Math.floor(parsed);
|
|
5136
|
+
}
|
|
4714
5137
|
async stop() {
|
|
4715
5138
|
await this.serviceCommands.stopService();
|
|
4716
5139
|
}
|
|
@@ -4784,10 +5207,10 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
4784
5207
|
`${this.logo} Interactive mode (type exit or Ctrl+C to quit)
|
|
4785
5208
|
`
|
|
4786
5209
|
);
|
|
4787
|
-
const historyFile =
|
|
5210
|
+
const historyFile = join7(getDataDir8(), "history", "cli_history");
|
|
4788
5211
|
const historyDir = resolve9(historyFile, "..");
|
|
4789
|
-
|
|
4790
|
-
const history =
|
|
5212
|
+
mkdirSync6(historyDir, { recursive: true });
|
|
5213
|
+
const history = existsSync10(historyFile) ? readFileSync8(historyFile, "utf-8").split("\n").filter(Boolean) : [];
|
|
4791
5214
|
const rl = createInterface2({
|
|
4792
5215
|
input: process.stdin,
|
|
4793
5216
|
output: process.stdout
|
|
@@ -4796,7 +5219,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
4796
5219
|
const merged = history.concat(
|
|
4797
5220
|
rl.history ?? []
|
|
4798
5221
|
);
|
|
4799
|
-
|
|
5222
|
+
writeFileSync5(historyFile, merged.join("\n"));
|
|
4800
5223
|
process.exit(0);
|
|
4801
5224
|
});
|
|
4802
5225
|
let running = true;
|
|
@@ -4972,8 +5395,8 @@ program.command("onboard").description(`Initialize ${APP_NAME5} configuration an
|
|
|
4972
5395
|
program.command("init").description(`Initialize ${APP_NAME5} configuration and workspace`).option("-f, --force", "Overwrite existing template files").action(async (opts) => runtime.init({ force: Boolean(opts.force) }));
|
|
4973
5396
|
program.command("gateway").description(`Start the ${APP_NAME5} gateway`).option("-p, --port <port>", "Gateway port", "18790").option("-v, --verbose", "Verbose output", false).option("--ui", "Enable UI server", false).option("--ui-port <port>", "UI port").option("--ui-open", "Open browser when UI starts", false).action(async (opts) => runtime.gateway(opts));
|
|
4974
5397
|
program.command("ui").description(`Start the ${APP_NAME5} UI with gateway`).option("--port <port>", "UI port").option("--no-open", "Disable opening browser").action(async (opts) => runtime.ui(opts));
|
|
4975
|
-
program.command("start").description(`Start the ${APP_NAME5} gateway + UI in the background`).option("--ui-port <port>", "UI port").option("--open", "Open browser after start", false).action(async (opts) => runtime.start(opts));
|
|
4976
|
-
program.command("restart").description(`Restart the ${APP_NAME5} background service`).option("--ui-port <port>", "UI port").option("--open", "Open browser after restart", false).action(async (opts) => runtime.restart(opts));
|
|
5398
|
+
program.command("start").description(`Start the ${APP_NAME5} gateway + UI in the background`).option("--ui-port <port>", "UI port").option("--start-timeout <ms>", "Maximum wait time for startup readiness in milliseconds").option("--open", "Open browser after start", false).action(async (opts) => runtime.start(opts));
|
|
5399
|
+
program.command("restart").description(`Restart the ${APP_NAME5} background service`).option("--ui-port <port>", "UI port").option("--start-timeout <ms>", "Maximum wait time for startup readiness in milliseconds").option("--open", "Open browser after restart", false).action(async (opts) => runtime.restart(opts));
|
|
4977
5400
|
program.command("serve").description(`Run the ${APP_NAME5} gateway + UI in the foreground`).option("--ui-port <port>", "UI port").option("--open", "Open browser after start", false).action(async (opts) => runtime.serve(opts));
|
|
4978
5401
|
program.command("stop").description(`Stop the ${APP_NAME5} background service`).action(async () => runtime.stop());
|
|
4979
5402
|
program.command("agent").description("Interact with the agent directly").option("-m, --message <message>", "Message to send to the agent").option("-s, --session <session>", "Session ID", "cli:default").option("--model <model>", "Session model override for this run").option("--no-markdown", "Disable Markdown rendering").action(async (opts) => runtime.agent(opts));
|