@4pm/cli 0.7.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
__require,
|
|
12
12
|
__toESM,
|
|
13
13
|
wrapper_default
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-I7WQWC33.js";
|
|
15
15
|
|
|
16
16
|
// ../../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
|
|
17
17
|
var require_react_production_min = __commonJS({
|
|
@@ -1353,7 +1353,7 @@ var require_react_development = __commonJS({
|
|
|
1353
1353
|
var dispatcher = resolveDispatcher();
|
|
1354
1354
|
return dispatcher.useReducer(reducer, initialArg, init);
|
|
1355
1355
|
}
|
|
1356
|
-
function
|
|
1356
|
+
function useRef2(initialValue) {
|
|
1357
1357
|
var dispatcher = resolveDispatcher();
|
|
1358
1358
|
return dispatcher.useRef(initialValue);
|
|
1359
1359
|
}
|
|
@@ -1369,7 +1369,7 @@ var require_react_development = __commonJS({
|
|
|
1369
1369
|
var dispatcher = resolveDispatcher();
|
|
1370
1370
|
return dispatcher.useLayoutEffect(create2, deps);
|
|
1371
1371
|
}
|
|
1372
|
-
function
|
|
1372
|
+
function useCallback2(callback, deps) {
|
|
1373
1373
|
var dispatcher = resolveDispatcher();
|
|
1374
1374
|
return dispatcher.useCallback(callback, deps);
|
|
1375
1375
|
}
|
|
@@ -1995,14 +1995,14 @@ var require_react_development = __commonJS({
|
|
|
1995
1995
|
var thenableResult = result;
|
|
1996
1996
|
var wasAwaited = false;
|
|
1997
1997
|
var thenable = {
|
|
1998
|
-
then: function(
|
|
1998
|
+
then: function(resolve8, reject) {
|
|
1999
1999
|
wasAwaited = true;
|
|
2000
2000
|
thenableResult.then(function(returnValue2) {
|
|
2001
2001
|
popActScope(prevActScopeDepth);
|
|
2002
2002
|
if (actScopeDepth === 0) {
|
|
2003
|
-
recursivelyFlushAsyncActWork(returnValue2,
|
|
2003
|
+
recursivelyFlushAsyncActWork(returnValue2, resolve8, reject);
|
|
2004
2004
|
} else {
|
|
2005
|
-
|
|
2005
|
+
resolve8(returnValue2);
|
|
2006
2006
|
}
|
|
2007
2007
|
}, function(error2) {
|
|
2008
2008
|
popActScope(prevActScopeDepth);
|
|
@@ -2032,20 +2032,20 @@ var require_react_development = __commonJS({
|
|
|
2032
2032
|
ReactCurrentActQueue.current = null;
|
|
2033
2033
|
}
|
|
2034
2034
|
var _thenable = {
|
|
2035
|
-
then: function(
|
|
2035
|
+
then: function(resolve8, reject) {
|
|
2036
2036
|
if (ReactCurrentActQueue.current === null) {
|
|
2037
2037
|
ReactCurrentActQueue.current = [];
|
|
2038
|
-
recursivelyFlushAsyncActWork(returnValue,
|
|
2038
|
+
recursivelyFlushAsyncActWork(returnValue, resolve8, reject);
|
|
2039
2039
|
} else {
|
|
2040
|
-
|
|
2040
|
+
resolve8(returnValue);
|
|
2041
2041
|
}
|
|
2042
2042
|
}
|
|
2043
2043
|
};
|
|
2044
2044
|
return _thenable;
|
|
2045
2045
|
} else {
|
|
2046
2046
|
var _thenable2 = {
|
|
2047
|
-
then: function(
|
|
2048
|
-
|
|
2047
|
+
then: function(resolve8, reject) {
|
|
2048
|
+
resolve8(returnValue);
|
|
2049
2049
|
}
|
|
2050
2050
|
};
|
|
2051
2051
|
return _thenable2;
|
|
@@ -2061,7 +2061,7 @@ var require_react_development = __commonJS({
|
|
|
2061
2061
|
actScopeDepth = prevActScopeDepth;
|
|
2062
2062
|
}
|
|
2063
2063
|
}
|
|
2064
|
-
function recursivelyFlushAsyncActWork(returnValue,
|
|
2064
|
+
function recursivelyFlushAsyncActWork(returnValue, resolve8, reject) {
|
|
2065
2065
|
{
|
|
2066
2066
|
var queue = ReactCurrentActQueue.current;
|
|
2067
2067
|
if (queue !== null) {
|
|
@@ -2070,16 +2070,16 @@ var require_react_development = __commonJS({
|
|
|
2070
2070
|
enqueueTask(function() {
|
|
2071
2071
|
if (queue.length === 0) {
|
|
2072
2072
|
ReactCurrentActQueue.current = null;
|
|
2073
|
-
|
|
2073
|
+
resolve8(returnValue);
|
|
2074
2074
|
} else {
|
|
2075
|
-
recursivelyFlushAsyncActWork(returnValue,
|
|
2075
|
+
recursivelyFlushAsyncActWork(returnValue, resolve8, reject);
|
|
2076
2076
|
}
|
|
2077
2077
|
});
|
|
2078
2078
|
} catch (error2) {
|
|
2079
2079
|
reject(error2);
|
|
2080
2080
|
}
|
|
2081
2081
|
} else {
|
|
2082
|
-
|
|
2082
|
+
resolve8(returnValue);
|
|
2083
2083
|
}
|
|
2084
2084
|
}
|
|
2085
2085
|
}
|
|
@@ -2136,7 +2136,7 @@ var require_react_development = __commonJS({
|
|
|
2136
2136
|
exports.memo = memo;
|
|
2137
2137
|
exports.startTransition = startTransition;
|
|
2138
2138
|
exports.unstable_act = act;
|
|
2139
|
-
exports.useCallback =
|
|
2139
|
+
exports.useCallback = useCallback2;
|
|
2140
2140
|
exports.useContext = useContext7;
|
|
2141
2141
|
exports.useDebugValue = useDebugValue;
|
|
2142
2142
|
exports.useDeferredValue = useDeferredValue;
|
|
@@ -2147,7 +2147,7 @@ var require_react_development = __commonJS({
|
|
|
2147
2147
|
exports.useLayoutEffect = useLayoutEffect2;
|
|
2148
2148
|
exports.useMemo = useMemo4;
|
|
2149
2149
|
exports.useReducer = useReducer;
|
|
2150
|
-
exports.useRef =
|
|
2150
|
+
exports.useRef = useRef2;
|
|
2151
2151
|
exports.useState = useState6;
|
|
2152
2152
|
exports.useSyncExternalStore = useSyncExternalStore;
|
|
2153
2153
|
exports.useTransition = useTransition;
|
|
@@ -24482,7 +24482,8 @@ function defaultProfileConfig() {
|
|
|
24482
24482
|
codexHome: [{ profile: ".codex", args: ["exec"], model: "" }],
|
|
24483
24483
|
antigravityHome: [],
|
|
24484
24484
|
autoUpdate: true,
|
|
24485
|
-
commandHistoryUploadMinutes: 10
|
|
24485
|
+
commandHistoryUploadMinutes: 10,
|
|
24486
|
+
autoClearIdleMinutes: 10
|
|
24486
24487
|
};
|
|
24487
24488
|
}
|
|
24488
24489
|
function ensureProfileConfig(dir2) {
|
|
@@ -24690,8 +24691,8 @@ async function runLink(serverUrl, explicitProfile) {
|
|
|
24690
24691
|
}
|
|
24691
24692
|
|
|
24692
24693
|
// src/commands/start.ts
|
|
24693
|
-
import { spawn as
|
|
24694
|
-
import { join as
|
|
24694
|
+
import { spawn as spawn6 } from "child_process";
|
|
24695
|
+
import { join as join23 } from "path";
|
|
24695
24696
|
|
|
24696
24697
|
// src/core/update.ts
|
|
24697
24698
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -24862,7 +24863,7 @@ async function checkAndUpdate(serverUrl, autoUpdate) {
|
|
|
24862
24863
|
|
|
24863
24864
|
// src/core/ws-client.ts
|
|
24864
24865
|
import { randomUUID } from "crypto";
|
|
24865
|
-
import { basename as
|
|
24866
|
+
import { basename as basename4, join as join19 } from "path";
|
|
24866
24867
|
|
|
24867
24868
|
// ../../22-packages/11-shared/51-utils/src/helpers.ts
|
|
24868
24869
|
function backoffJitterMs(attempt, baseMs = 500, maxMs = 1e4) {
|
|
@@ -24965,6 +24966,18 @@ var PermissionCode = {
|
|
|
24965
24966
|
PROJECT_AI_ASSIST: "project.ai_assist",
|
|
24966
24967
|
PROJECT_MEMO_READ: "project.memo_read",
|
|
24967
24968
|
PROJECT_MEMO_WRITE: "project.memo_write",
|
|
24969
|
+
// Git tab (ADR-0151): read git (branches/graph/log/status/diff) via command dispatch;
|
|
24970
|
+
// write = commit/push/pull/checkout/merge/PR + fs.write (machine-0027)
|
|
24971
|
+
PROJECT_GIT: "project.git",
|
|
24972
|
+
PROJECT_GIT_WRITE: "project.git_write",
|
|
24973
|
+
// Autonomous mode (ADR-0152): settings/start-pause/VERIFY-approve/post-USER_TODO
|
|
24974
|
+
PROJECT_AUTONOMOUS: "project.autonomous",
|
|
24975
|
+
// Subagents & skills management (ADR-0153): create/edit/delete .claude/agents + skills
|
|
24976
|
+
PROJECT_AGENTS: "project.agents",
|
|
24977
|
+
// Security & placeholder management (ADR-0154): AI_SECURITY/AI_PLACEHOLDER docs + write-only secrets
|
|
24978
|
+
PROJECT_SECRETS: "project.secrets",
|
|
24979
|
+
// RAG capability install (ADR-0156): install/manage the worker's RAG engine
|
|
24980
|
+
PROJECT_RAG: "project.rag",
|
|
24968
24981
|
// permission
|
|
24969
24982
|
PERMISSION_LIST: "permission.list",
|
|
24970
24983
|
PERMISSION_READ: "permission.read",
|
|
@@ -25064,6 +25077,12 @@ var ROLE_DEFAULT_PERMISSIONS = {
|
|
|
25064
25077
|
P.PROJECT_AI_ASSIST,
|
|
25065
25078
|
P.PROJECT_MEMO_READ,
|
|
25066
25079
|
P.PROJECT_MEMO_WRITE,
|
|
25080
|
+
P.PROJECT_GIT,
|
|
25081
|
+
P.PROJECT_GIT_WRITE,
|
|
25082
|
+
P.PROJECT_AUTONOMOUS,
|
|
25083
|
+
P.PROJECT_AGENTS,
|
|
25084
|
+
P.PROJECT_SECRETS,
|
|
25085
|
+
P.PROJECT_RAG,
|
|
25067
25086
|
P.PERMISSION_LIST,
|
|
25068
25087
|
P.PERMISSION_READ,
|
|
25069
25088
|
P.MACHINE_PAIR,
|
|
@@ -25088,6 +25107,13 @@ var ROLE_DEFAULT_PERMISSIONS = {
|
|
|
25088
25107
|
P.PROJECT_READ,
|
|
25089
25108
|
P.PROJECT_MEMO_READ,
|
|
25090
25109
|
P.PROJECT_MEMO_WRITE,
|
|
25110
|
+
P.PROJECT_GIT,
|
|
25111
|
+
// manage subagents/skills (dev artifacts) — ADR-0153
|
|
25112
|
+
P.PROJECT_AGENTS,
|
|
25113
|
+
// manage security/placeholder + set (write-only) secrets — ADR-0154
|
|
25114
|
+
P.PROJECT_SECRETS,
|
|
25115
|
+
// install/manage the worker RAG engine — ADR-0156
|
|
25116
|
+
P.PROJECT_RAG,
|
|
25091
25117
|
P.INTEGRATION_READ
|
|
25092
25118
|
],
|
|
25093
25119
|
[Role.QA]: [
|
|
@@ -25098,6 +25124,7 @@ var ROLE_DEFAULT_PERMISSIONS = {
|
|
|
25098
25124
|
P.PROJECT_READ,
|
|
25099
25125
|
P.PROJECT_MEMO_READ,
|
|
25100
25126
|
P.PROJECT_MEMO_WRITE,
|
|
25127
|
+
P.PROJECT_GIT,
|
|
25101
25128
|
P.INTEGRATION_READ
|
|
25102
25129
|
],
|
|
25103
25130
|
[Role.BA]: [
|
|
@@ -25108,6 +25135,7 @@ var ROLE_DEFAULT_PERMISSIONS = {
|
|
|
25108
25135
|
P.PROJECT_READ,
|
|
25109
25136
|
P.PROJECT_MEMO_READ,
|
|
25110
25137
|
P.PROJECT_MEMO_WRITE,
|
|
25138
|
+
P.PROJECT_GIT,
|
|
25111
25139
|
P.INTEGRATION_READ
|
|
25112
25140
|
],
|
|
25113
25141
|
[Role.MACHINE]: [
|
|
@@ -25115,6 +25143,13 @@ var ROLE_DEFAULT_PERMISSIONS = {
|
|
|
25115
25143
|
P.PROJECT_READ,
|
|
25116
25144
|
P.PROJECT_MEMO_READ,
|
|
25117
25145
|
P.PROJECT_MEMO_WRITE,
|
|
25146
|
+
// git read + write so the autonomous merge cycle (auto-cycle.md) can run (ADR-0151)
|
|
25147
|
+
P.PROJECT_GIT,
|
|
25148
|
+
P.PROJECT_GIT_WRITE,
|
|
25149
|
+
// autonomous mode control on its own physic project (ADR-0152)
|
|
25150
|
+
P.PROJECT_AUTONOMOUS,
|
|
25151
|
+
// manage its own subagents/skills (ADR-0153)
|
|
25152
|
+
P.PROJECT_AGENTS,
|
|
25118
25153
|
// view one's own worker/cli + configure the physic project
|
|
25119
25154
|
P.MACHINE_LIST,
|
|
25120
25155
|
P.MACHINE_PHYSIC_MANAGE,
|
|
@@ -25142,7 +25177,13 @@ var PROJECT_SCOPE_CHECKED_CODES = [
|
|
|
25142
25177
|
P.PROJECT_PAUSE,
|
|
25143
25178
|
P.PROJECT_CLOSE,
|
|
25144
25179
|
P.PROJECT_PAUSE_MEMBER,
|
|
25145
|
-
P.PROJECT_AI_ASSIST
|
|
25180
|
+
P.PROJECT_AI_ASSIST,
|
|
25181
|
+
P.PROJECT_GIT,
|
|
25182
|
+
P.PROJECT_GIT_WRITE,
|
|
25183
|
+
P.PROJECT_AUTONOMOUS,
|
|
25184
|
+
P.PROJECT_AGENTS,
|
|
25185
|
+
P.PROJECT_SECRETS,
|
|
25186
|
+
P.PROJECT_RAG
|
|
25146
25187
|
];
|
|
25147
25188
|
var SELF_SERVICE_CODES = [
|
|
25148
25189
|
P.AUTH_LOGOUT,
|
|
@@ -25182,6 +25223,12 @@ var TEAM_GRANTABLE_CODES = [
|
|
|
25182
25223
|
P.PROJECT_AI_ASSIST,
|
|
25183
25224
|
P.PROJECT_MEMO_READ,
|
|
25184
25225
|
P.PROJECT_MEMO_WRITE,
|
|
25226
|
+
P.PROJECT_GIT,
|
|
25227
|
+
P.PROJECT_GIT_WRITE,
|
|
25228
|
+
P.PROJECT_AUTONOMOUS,
|
|
25229
|
+
P.PROJECT_AGENTS,
|
|
25230
|
+
P.PROJECT_SECRETS,
|
|
25231
|
+
P.PROJECT_RAG,
|
|
25185
25232
|
P.COMMAND_EXECUTE,
|
|
25186
25233
|
P.COMMAND_READ,
|
|
25187
25234
|
P.MACHINE_LIST,
|
|
@@ -25203,6 +25250,12 @@ var PROJECT_GRANTABLE_CODES = [
|
|
|
25203
25250
|
P.PROJECT_AI_ASSIST,
|
|
25204
25251
|
P.PROJECT_MEMO_READ,
|
|
25205
25252
|
P.PROJECT_MEMO_WRITE,
|
|
25253
|
+
P.PROJECT_GIT,
|
|
25254
|
+
P.PROJECT_GIT_WRITE,
|
|
25255
|
+
P.PROJECT_AUTONOMOUS,
|
|
25256
|
+
P.PROJECT_AGENTS,
|
|
25257
|
+
P.PROJECT_SECRETS,
|
|
25258
|
+
P.PROJECT_RAG,
|
|
25206
25259
|
P.COMMAND_EXECUTE,
|
|
25207
25260
|
P.COMMAND_READ,
|
|
25208
25261
|
P.MACHINE_PHYSIC_MANAGE
|
|
@@ -29315,7 +29368,7 @@ var pinnedFilterSchema = external_exports.enum(["true", "false"]).transform((v)
|
|
|
29315
29368
|
var deletedFilterSchema = external_exports.enum(["true", "false"]).transform((v) => v === "true").optional();
|
|
29316
29369
|
|
|
29317
29370
|
// ../../22-packages/11-shared/41-validation/src/schemas.ts
|
|
29318
|
-
var passwordSchema = external_exports.string().min(8, "password: at least 8 characters").max(
|
|
29371
|
+
var passwordSchema = external_exports.string().min(8, "password: at least 8 characters").max(32, "password: at most 32 characters").regex(/[a-zA-Z]/, "password: needs at least 1 letter").regex(/[0-9]/, "password: needs at least 1 digit");
|
|
29319
29372
|
var usernameSchema = external_exports.string().min(3).max(32).regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/, "username: invalid characters");
|
|
29320
29373
|
var emailSchema = external_exports.string().email().max(254).toLowerCase();
|
|
29321
29374
|
var phoneSchema = external_exports.string().regex(/^\+?[0-9]{8,15}$/, "phone: invalid format");
|
|
@@ -29344,6 +29397,9 @@ var registerRequestSchema = external_exports.object({
|
|
|
29344
29397
|
var mailTokenRequestSchema = external_exports.object({
|
|
29345
29398
|
token: hexTokenSchema
|
|
29346
29399
|
});
|
|
29400
|
+
var resendVerifyRequestSchema = external_exports.object({
|
|
29401
|
+
email: emailSchema
|
|
29402
|
+
});
|
|
29347
29403
|
var loginRequestSchema = external_exports.object({
|
|
29348
29404
|
identifier: external_exports.string().min(1).max(254),
|
|
29349
29405
|
password: external_exports.string().min(1).max(72),
|
|
@@ -29488,6 +29544,9 @@ var createProjectRequestSchema = external_exports.object({
|
|
|
29488
29544
|
description: external_exports.string().max(2e3).optional(),
|
|
29489
29545
|
machineUserId: external_exports.string().uuid().optional()
|
|
29490
29546
|
});
|
|
29547
|
+
var copyProjectRequestSchema = external_exports.object({
|
|
29548
|
+
name: external_exports.string().min(1).max(100)
|
|
29549
|
+
});
|
|
29491
29550
|
var DEFAULT_OUTBOUND_RULES = {
|
|
29492
29551
|
secret: [
|
|
29493
29552
|
String.raw`/\b(api[_-]?key|secret|password|passwd|access[_-]?token|bearer)\b\s*[:=]\s*\S+/i`,
|
|
@@ -29518,7 +29577,15 @@ var updateProjectRequestSchema = external_exports.object({
|
|
|
29518
29577
|
// Messenger retention in days (0 = keep forever) — manager only (ADR-0078).
|
|
29519
29578
|
messengerRetentionDays: external_exports.number().int().min(0).max(3650).optional(),
|
|
29520
29579
|
// Project settings (ADR-0081) — key-level shallow-merge; `tokens` clamped on read.
|
|
29521
|
-
settings: external_exports.record(external_exports.unknown()).optional()
|
|
29580
|
+
settings: external_exports.record(external_exports.unknown()).optional(),
|
|
29581
|
+
// Identity ↔ spec sync (ADR-0148): the Settings tab edits the spec basic group
|
|
29582
|
+
// (id = spec slug, name, desc). The server merges these into `spec` (READY) or
|
|
29583
|
+
// `specDraft` (DRAFT) so the Content → Spec tab / wizard stays consistent.
|
|
29584
|
+
specBasic: external_exports.object({
|
|
29585
|
+
id: external_exports.string().max(200),
|
|
29586
|
+
name: external_exports.string().max(100),
|
|
29587
|
+
desc: external_exports.string().max(2e3)
|
|
29588
|
+
}).partial().optional()
|
|
29522
29589
|
});
|
|
29523
29590
|
var MEMO_MAX_LENGTH = 65536;
|
|
29524
29591
|
var putMemoRequestSchema = external_exports.object({
|
|
@@ -29705,6 +29772,57 @@ var listMachineLinksQuerySchema = baseRequestSchema.extend({
|
|
|
29705
29772
|
var updateMachineLinkRequestSchema = external_exports.object({
|
|
29706
29773
|
hashcode3ExpiresAt: external_exports.string().datetime().nullable()
|
|
29707
29774
|
});
|
|
29775
|
+
var configWriteRequestSchema = external_exports.object({
|
|
29776
|
+
config: external_exports.string().max(256 * 1024)
|
|
29777
|
+
});
|
|
29778
|
+
var FILE_WRITE_MAX_BYTES = 2 * 1024 * 1024;
|
|
29779
|
+
var fsWriteRequestSchema = external_exports.object({
|
|
29780
|
+
/** Path relative to the physic-project root; a `..` escaping the root is rejected on the cli. */
|
|
29781
|
+
path: external_exports.string().min(1).max(1024),
|
|
29782
|
+
/** Full new UTF-8 content (replaces the file wholesale). */
|
|
29783
|
+
content: external_exports.string().max(FILE_WRITE_MAX_BYTES)
|
|
29784
|
+
});
|
|
29785
|
+
var autonomousWriteRequestSchema = external_exports.discriminatedUnion("kind", [
|
|
29786
|
+
external_exports.object({ kind: external_exports.literal("settings"), settings: external_exports.string().max(64 * 1024) }),
|
|
29787
|
+
external_exports.object({ kind: external_exports.literal("approvals"), taskId: external_exports.string().min(1).max(64), approved: external_exports.boolean() }),
|
|
29788
|
+
external_exports.object({ kind: external_exports.literal("userTodo"), content: external_exports.string().min(1).max(16 * 1024) }),
|
|
29789
|
+
external_exports.object({ kind: external_exports.literal("cron"), action: external_exports.enum(["install", "uninstall"]) })
|
|
29790
|
+
]);
|
|
29791
|
+
var autonomousLogsQuerySchema = external_exports.object({
|
|
29792
|
+
date: external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional()
|
|
29793
|
+
});
|
|
29794
|
+
var agentNameSchema = external_exports.string().min(1).max(64).regex(/^[\w.-]+$/);
|
|
29795
|
+
var agentReadQuerySchema = external_exports.object({
|
|
29796
|
+
kind: external_exports.enum(["subagent", "skill"]),
|
|
29797
|
+
name: agentNameSchema
|
|
29798
|
+
});
|
|
29799
|
+
var ragInstallRequestSchema = external_exports.object({
|
|
29800
|
+
model: external_exports.string().min(1).max(200)
|
|
29801
|
+
});
|
|
29802
|
+
var ragQueryQuerySchema = external_exports.object({
|
|
29803
|
+
q: external_exports.string().min(1).max(2e3),
|
|
29804
|
+
k: external_exports.coerce.number().int().min(1).max(20).optional()
|
|
29805
|
+
});
|
|
29806
|
+
var graphBuildQuerySchema = external_exports.object({
|
|
29807
|
+
mode: external_exports.enum(["docs", "code"])
|
|
29808
|
+
});
|
|
29809
|
+
var secretKeySchema = external_exports.string().min(1).max(128).regex(/^[\w.-]+$/);
|
|
29810
|
+
var secretsWriteRequestSchema = external_exports.discriminatedUnion("kind", [
|
|
29811
|
+
external_exports.object({ kind: external_exports.literal("security"), content: external_exports.string().max(128 * 1024) }),
|
|
29812
|
+
external_exports.object({ kind: external_exports.literal("placeholder"), content: external_exports.string().max(128 * 1024) }),
|
|
29813
|
+
external_exports.object({ kind: external_exports.literal("secret"), key: secretKeySchema, value: external_exports.string().max(8192) }),
|
|
29814
|
+
external_exports.object({ kind: external_exports.literal("secretDelete"), key: secretKeySchema })
|
|
29815
|
+
]);
|
|
29816
|
+
var agentWriteRequestSchema = external_exports.object({
|
|
29817
|
+
kind: external_exports.enum(["subagent", "skill"]),
|
|
29818
|
+
name: agentNameSchema,
|
|
29819
|
+
action: external_exports.enum(["write", "delete"]),
|
|
29820
|
+
content: external_exports.string().max(128 * 1024).optional()
|
|
29821
|
+
}).superRefine((v, ctx) => {
|
|
29822
|
+
if (v.action === "write" && (v.content === void 0 || v.content.length === 0)) {
|
|
29823
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["content"], message: "content is required for write" });
|
|
29824
|
+
}
|
|
29825
|
+
});
|
|
29708
29826
|
|
|
29709
29827
|
// ../../22-packages/11-shared/31-dto/src/command.ts
|
|
29710
29828
|
var COMMAND_MAX_LEN = 8e3;
|
|
@@ -29725,7 +29843,14 @@ var dispatchCommandRequestSchema = external_exports.object({
|
|
|
29725
29843
|
* Short human label shown to OTHER tabs watching this cli's activity feed (ADR-0101):
|
|
29726
29844
|
* e.g. "AI review" so the Console shows a friendly line instead of the raw prompt.
|
|
29727
29845
|
*/
|
|
29728
|
-
label: external_exports.string().max(120).optional()
|
|
29846
|
+
label: external_exports.string().max(120).optional(),
|
|
29847
|
+
/**
|
|
29848
|
+
* Git tab (ADR-0151): when set, `command` is a `git`/`gh`/`glab` command run on the
|
|
29849
|
+
* worker for the Git tab. The server routes the permission to `project.git` (`read`) /
|
|
29850
|
+
* `project.git_write` (`write`) instead of `command.execute`, and validates `command`
|
|
29851
|
+
* against the git allowlist (`isGitCommandAllowed`). Absent ⇒ normal console dispatch.
|
|
29852
|
+
*/
|
|
29853
|
+
gitOp: external_exports.enum(["read", "write"]).optional()
|
|
29729
29854
|
}).superRefine((v, ctx) => {
|
|
29730
29855
|
if (!v.ai && v.command.length > COMMAND_MAX_LEN) {
|
|
29731
29856
|
ctx.addIssue({
|
|
@@ -29872,12 +29997,6 @@ var checkoutRequestSchema = external_exports.object({
|
|
|
29872
29997
|
planCode: external_exports.string().min(1).max(50),
|
|
29873
29998
|
provider: external_exports.enum([PaymentProvider.STRIPE, PaymentProvider.PAYPAL]).optional()
|
|
29874
29999
|
});
|
|
29875
|
-
var upsertPlanRequestSchema = external_exports.object({
|
|
29876
|
-
name: external_exports.string().min(1).max(100),
|
|
29877
|
-
price: external_exports.number().int().min(0).default(0),
|
|
29878
|
-
limits: external_exports.array(planLimitSchema).default([]),
|
|
29879
|
-
entitlements: planEntitlementsSchema.optional()
|
|
29880
|
-
});
|
|
29881
30000
|
var assignPlanRequestSchema = external_exports.object({
|
|
29882
30001
|
planCode: external_exports.string().min(1).max(50),
|
|
29883
30002
|
/** Cycle length in days (defaults to 30). */
|
|
@@ -29948,8 +30067,36 @@ var WsChannels = {
|
|
|
29948
30067
|
...WsChannel,
|
|
29949
30068
|
FS_LIST: "fs.list",
|
|
29950
30069
|
FS_READ: "fs.read",
|
|
30070
|
+
// server → cli (reply): write a file, path-clamped to the physic root (machine-0027,
|
|
30071
|
+
// ADR-0151) — the Git tab's manual conflict resolution (`project.git_write`); the only
|
|
30072
|
+
// git-tab write not carried by a git command over command dispatch.
|
|
30073
|
+
FS_WRITE: "fs.write",
|
|
29951
30074
|
GIT_DIFF: "git.diff",
|
|
29952
30075
|
GIT_ENV: "git.env",
|
|
30076
|
+
// server → cli (reply): read/write the paired profile's config.json (machine-0025/0026,
|
|
30077
|
+
// ADR-0141) — the machine-user Settings tab syncs the worker cli's config.
|
|
30078
|
+
CONFIG_READ: "config.read",
|
|
30079
|
+
CONFIG_WRITE: "config.write",
|
|
30080
|
+
// autonomous mode control (ADR-0152): read settings+status+books+approvals, tail logs, and
|
|
30081
|
+
// write {settings|approvals|userTodo|cron} — the dashboard drives the worker's autonomous engine.
|
|
30082
|
+
AUTONOMOUS_READ: "autonomous.read",
|
|
30083
|
+
AUTONOMOUS_WRITE: "autonomous.write",
|
|
30084
|
+
AUTONOMOUS_LOGS: "autonomous.logs",
|
|
30085
|
+
// subagents & skills management (ADR-0153): list/read/write .claude/agents + .claude/skills
|
|
30086
|
+
AGENTS_LIST: "agents.list",
|
|
30087
|
+
AGENTS_READ: "agents.read",
|
|
30088
|
+
AGENTS_WRITE: "agents.write",
|
|
30089
|
+
// security & placeholder management (ADR-0154): AI_SECURITY/AI_PLACEHOLDER + write-only secrets
|
|
30090
|
+
SECRETS_READ: "secrets.read",
|
|
30091
|
+
SECRETS_WRITE: "secrets.write",
|
|
30092
|
+
// docs & code dependency graph (ADR-0155): build a {nodes,edges,orphans} graph per mode
|
|
30093
|
+
GRAPH_BUILD: "graph.build",
|
|
30094
|
+
// RAG capability + install (ADR-0156): probe the worker + install a worker-tuned model
|
|
30095
|
+
RAG_STATUS: "rag.status",
|
|
30096
|
+
RAG_INSTALL: "rag.install",
|
|
30097
|
+
// RAG index + query (ADR-0157): reindex docs into sqlite-vec, semantic query
|
|
30098
|
+
RAG_REINDEX: "rag.reindex",
|
|
30099
|
+
RAG_QUERY: "rag.query",
|
|
29953
30100
|
// git history browse (read-through cli — ADR-0089): repos on the physic project,
|
|
29954
30101
|
// commit log, changed files of a commit, parent↔commit diff of one file.
|
|
29955
30102
|
GIT_REPOS: "git.repos",
|
|
@@ -29967,6 +30114,11 @@ var WsChannels = {
|
|
|
29967
30114
|
PHYSIC_DELETE: "physic.delete",
|
|
29968
30115
|
// command lifecycle
|
|
29969
30116
|
COMMAND_ANNOUNCE: "command.announce",
|
|
30117
|
+
// console transcript sync (ADR-0150): the cli streams its authoritative SessionBus
|
|
30118
|
+
// transcript so the web Console renders it verbatim; gated by console.watch (server → cli:
|
|
30119
|
+
// whether a web viewer is attached) so an unwatched cli syncs nothing.
|
|
30120
|
+
CONSOLE_SYNC: "console.sync",
|
|
30121
|
+
CONSOLE_WATCH: "console.watch",
|
|
29970
30122
|
// AI spec-assist (suggest/review/compose) moved off dedicated ai.* channels to
|
|
29971
30123
|
// command.dispatch({ ai:true }) → the profile-failover AI path (ADR-0100).
|
|
29972
30124
|
// quota & usage (ADR-0020)
|
|
@@ -30104,7 +30256,7 @@ var OutputBatcher = class {
|
|
|
30104
30256
|
};
|
|
30105
30257
|
|
|
30106
30258
|
// src/core/ws-client.ts
|
|
30107
|
-
import { existsSync as
|
|
30259
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, rmSync as rmSync4 } from "fs";
|
|
30108
30260
|
import { randomUUID as randomCommandId } from "crypto";
|
|
30109
30261
|
|
|
30110
30262
|
// src/core/update-scheduler.ts
|
|
@@ -30347,7 +30499,7 @@ async function runCommand(dispatch, emit) {
|
|
|
30347
30499
|
});
|
|
30348
30500
|
child.stdout?.on("data", (data) => batcher.push(data.toString("utf8")));
|
|
30349
30501
|
child.stderr?.on("data", (data) => batcher.push(data.toString("utf8")));
|
|
30350
|
-
await new Promise((
|
|
30502
|
+
await new Promise((resolve8) => {
|
|
30351
30503
|
child.on("close", (exitCode) => {
|
|
30352
30504
|
batcher.flush();
|
|
30353
30505
|
emit({
|
|
@@ -30357,7 +30509,7 @@ async function runCommand(dispatch, emit) {
|
|
|
30357
30509
|
done: true,
|
|
30358
30510
|
exitCode: exitCode ?? -1
|
|
30359
30511
|
});
|
|
30360
|
-
|
|
30512
|
+
resolve8();
|
|
30361
30513
|
});
|
|
30362
30514
|
child.on("error", (err) => {
|
|
30363
30515
|
emit({
|
|
@@ -30368,7 +30520,7 @@ async function runCommand(dispatch, emit) {
|
|
|
30368
30520
|
done: true,
|
|
30369
30521
|
exitCode: -1
|
|
30370
30522
|
});
|
|
30371
|
-
|
|
30523
|
+
resolve8();
|
|
30372
30524
|
});
|
|
30373
30525
|
});
|
|
30374
30526
|
}
|
|
@@ -30448,10 +30600,10 @@ function isSessionLimit(text) {
|
|
|
30448
30600
|
);
|
|
30449
30601
|
}
|
|
30450
30602
|
function runAttempt(commandId, cmd, args, cwd2, env3, onChunk) {
|
|
30451
|
-
return new Promise((
|
|
30603
|
+
return new Promise((resolve8) => {
|
|
30452
30604
|
void runCommand({ commandId, cmd, args, path: cwd2, env: env3 }, (out) => {
|
|
30453
30605
|
if (out.chunk) onChunk(out.chunk);
|
|
30454
|
-
if (out.done)
|
|
30606
|
+
if (out.done) resolve8(out.exitCode ?? -1);
|
|
30455
30607
|
});
|
|
30456
30608
|
});
|
|
30457
30609
|
}
|
|
@@ -30629,16 +30781,16 @@ async function fetchUsage(token) {
|
|
|
30629
30781
|
}
|
|
30630
30782
|
}
|
|
30631
30783
|
function runUsageCli(dir2, aiCli2) {
|
|
30632
|
-
return new Promise((
|
|
30784
|
+
return new Promise((resolve8) => {
|
|
30633
30785
|
let out = "";
|
|
30634
30786
|
let child = null;
|
|
30635
30787
|
const finish = () => {
|
|
30636
30788
|
clearTimeout(timer);
|
|
30637
|
-
|
|
30789
|
+
resolve8(out);
|
|
30638
30790
|
};
|
|
30639
30791
|
const timer = setTimeout(() => {
|
|
30640
30792
|
child?.kill();
|
|
30641
|
-
|
|
30793
|
+
resolve8(out);
|
|
30642
30794
|
}, 3e4);
|
|
30643
30795
|
timer.unref?.();
|
|
30644
30796
|
try {
|
|
@@ -31069,24 +31221,738 @@ async function readWorkerFile(path) {
|
|
|
31069
31221
|
}
|
|
31070
31222
|
}
|
|
31071
31223
|
|
|
31072
|
-
// src/core/
|
|
31073
|
-
import {
|
|
31074
|
-
import {
|
|
31075
|
-
|
|
31224
|
+
// src/core/fs-write.ts
|
|
31225
|
+
import { mkdir as mkdir2, writeFile } from "fs/promises";
|
|
31226
|
+
import { dirname as dirname3, resolve as resolve3, sep as sep3 } from "path";
|
|
31227
|
+
var MAX_BYTES2 = 2 * 1024 * 1024;
|
|
31228
|
+
function resolveInRoot(root, relPath) {
|
|
31229
|
+
if (!root) return null;
|
|
31230
|
+
const base = resolve3(root);
|
|
31231
|
+
const target = resolve3(base, relPath || ".");
|
|
31232
|
+
return target === base || target.startsWith(base + sep3) ? target : null;
|
|
31233
|
+
}
|
|
31234
|
+
async function writeWorkerFile(root, relPath, content) {
|
|
31235
|
+
const target = resolveInRoot(root, relPath);
|
|
31236
|
+
if (!target) {
|
|
31237
|
+
return { ok: false, path: relPath, bytes: 0, error: "path escapes the project root" };
|
|
31238
|
+
}
|
|
31239
|
+
const bytes = Buffer.byteLength(content, "utf8");
|
|
31240
|
+
if (bytes > MAX_BYTES2) {
|
|
31241
|
+
return { ok: false, path: target, bytes: 0, error: `content too large (max ${MAX_BYTES2} bytes)` };
|
|
31242
|
+
}
|
|
31243
|
+
try {
|
|
31244
|
+
await mkdir2(dirname3(target), { recursive: true });
|
|
31245
|
+
await writeFile(target, content, "utf8");
|
|
31246
|
+
return { ok: true, path: target, bytes };
|
|
31247
|
+
} catch (err) {
|
|
31248
|
+
return { ok: false, path: target, bytes: 0, error: err instanceof Error ? err.message : String(err) };
|
|
31249
|
+
}
|
|
31250
|
+
}
|
|
31251
|
+
|
|
31252
|
+
// src/core/autonomous.ts
|
|
31253
|
+
import { readFile as readFile2, writeFile as writeFile2, chmod } from "fs/promises";
|
|
31254
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
31255
|
+
import { execFile as execFile2, spawn as spawn4 } from "child_process";
|
|
31256
|
+
import { join as join12 } from "path";
|
|
31076
31257
|
import { promisify as promisify2 } from "util";
|
|
31077
31258
|
var run2 = promisify2(execFile2);
|
|
31259
|
+
var SETTINGS_REL = ".claude/.autonomous.settings.json";
|
|
31260
|
+
var HISTORIES_REL = ".claude/.autonomous.histories.json";
|
|
31261
|
+
var APPROVALS_REL = ".claude/.autonomous.approvals.json";
|
|
31262
|
+
var TICK_REL = ".claude/hooks/autonomous-tick.sh";
|
|
31263
|
+
var LOG_DIR_REL = ".claude/logs";
|
|
31264
|
+
var BOOK_FILES = {
|
|
31265
|
+
userTodo: "USER_TODO.md",
|
|
31266
|
+
aiTodo: "AI_TODO.md",
|
|
31267
|
+
aiProgress: "AI_PROGRESS.md",
|
|
31268
|
+
aiDone: "AI_DONE.md",
|
|
31269
|
+
userQa: "USER_QA.md"
|
|
31270
|
+
};
|
|
31271
|
+
var DEFAULT_CRON = "*/10 * * * *";
|
|
31272
|
+
async function readText(path, fallback = "") {
|
|
31273
|
+
try {
|
|
31274
|
+
return await readFile2(path, "utf8");
|
|
31275
|
+
} catch {
|
|
31276
|
+
return fallback;
|
|
31277
|
+
}
|
|
31278
|
+
}
|
|
31279
|
+
function parseJson(text) {
|
|
31280
|
+
try {
|
|
31281
|
+
const v = JSON.parse(text);
|
|
31282
|
+
return v && typeof v === "object" ? v : null;
|
|
31283
|
+
} catch {
|
|
31284
|
+
return null;
|
|
31285
|
+
}
|
|
31286
|
+
}
|
|
31287
|
+
function tickScript(root) {
|
|
31288
|
+
return join12(root, TICK_REL);
|
|
31289
|
+
}
|
|
31290
|
+
async function crontabList() {
|
|
31291
|
+
try {
|
|
31292
|
+
const { stdout } = await run2("crontab", ["-l"], { timeout: 1e4 });
|
|
31293
|
+
return stdout;
|
|
31294
|
+
} catch {
|
|
31295
|
+
return "";
|
|
31296
|
+
}
|
|
31297
|
+
}
|
|
31298
|
+
async function crontabSet(content) {
|
|
31299
|
+
return new Promise((resolve8) => {
|
|
31300
|
+
const child = spawn4("crontab", ["-"], { stdio: ["pipe", "ignore", "ignore"] });
|
|
31301
|
+
child.on("error", () => resolve8(false));
|
|
31302
|
+
child.on("close", (code) => resolve8(code === 0));
|
|
31303
|
+
child.stdin.end(content.endsWith("\n") ? content : content + "\n");
|
|
31304
|
+
});
|
|
31305
|
+
}
|
|
31306
|
+
async function cronInstalled(root) {
|
|
31307
|
+
const script = tickScript(root);
|
|
31308
|
+
return (await crontabList()).split("\n").some((l) => l.includes(script) && !l.trim().startsWith("#"));
|
|
31309
|
+
}
|
|
31310
|
+
async function getAutonomousStatus(root) {
|
|
31311
|
+
const settings = parseJson(await readText(join12(root, SETTINGS_REL))) ?? {};
|
|
31312
|
+
const hist = parseJson(await readText(join12(root, HISTORIES_REL))) ?? {};
|
|
31313
|
+
const records = Array.isArray(hist.records) ? hist.records : [];
|
|
31314
|
+
const last = records[records.length - 1];
|
|
31315
|
+
const ticks = hist.ticks ?? {};
|
|
31316
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
31317
|
+
return {
|
|
31318
|
+
installed: await cronInstalled(root),
|
|
31319
|
+
paused: settings.paused === true,
|
|
31320
|
+
cronSchedule: typeof settings.cron_schedule === "string" ? settings.cron_schedule : DEFAULT_CRON,
|
|
31321
|
+
lastTickAt: last && typeof last.ts === "string" ? last.ts : null,
|
|
31322
|
+
lastResult: last && typeof last.status === "string" ? last.status : null,
|
|
31323
|
+
consecutiveFails: typeof hist.consecutive_fails === "number" ? hist.consecutive_fails : 0,
|
|
31324
|
+
todayTicks: ticks.day === today && typeof ticks.count === "number" ? ticks.count : 0
|
|
31325
|
+
};
|
|
31326
|
+
}
|
|
31327
|
+
function isAutonomousRunning(root) {
|
|
31328
|
+
try {
|
|
31329
|
+
const s = JSON.parse(readFileSync12(join12(root, SETTINGS_REL), "utf8"));
|
|
31330
|
+
if (s.paused === true) return false;
|
|
31331
|
+
const h = JSON.parse(readFileSync12(join12(root, HISTORIES_REL), "utf8"));
|
|
31332
|
+
const ts = h.records?.[h.records.length - 1]?.ts;
|
|
31333
|
+
if (!ts) return false;
|
|
31334
|
+
const t = Date.parse(ts.replace(" ", "T"));
|
|
31335
|
+
return Number.isFinite(t) && Date.now() - t < 6 * 3600 * 1e3;
|
|
31336
|
+
} catch {
|
|
31337
|
+
return false;
|
|
31338
|
+
}
|
|
31339
|
+
}
|
|
31340
|
+
async function readAutonomous(root) {
|
|
31341
|
+
const [settings, approvals, status, ...books] = await Promise.all([
|
|
31342
|
+
readText(join12(root, SETTINGS_REL)),
|
|
31343
|
+
readText(join12(root, APPROVALS_REL), "{}"),
|
|
31344
|
+
getAutonomousStatus(root),
|
|
31345
|
+
...Object.values(BOOK_FILES).map((f) => readText(join12(root, f)))
|
|
31346
|
+
]);
|
|
31347
|
+
const keys = Object.keys(BOOK_FILES);
|
|
31348
|
+
const bookMap = {};
|
|
31349
|
+
keys.forEach((k, i) => bookMap[k] = books[i] ?? "");
|
|
31350
|
+
return { settings, status, books: bookMap, approvals };
|
|
31351
|
+
}
|
|
31352
|
+
async function readAutonomousLogs(root, date) {
|
|
31353
|
+
const day = date && /^\d{4}-\d{2}-\d{2}$/.test(date) ? date : (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
31354
|
+
const text = await readText(join12(root, LOG_DIR_REL, `autonomous-tick-${day}.log`));
|
|
31355
|
+
const lines = text ? text.split("\n").filter((l) => l.length > 0).slice(-500) : [];
|
|
31356
|
+
return { date: day, lines };
|
|
31357
|
+
}
|
|
31358
|
+
async function installCron(root) {
|
|
31359
|
+
const script = tickScript(root);
|
|
31360
|
+
const status = await getAutonomousStatus(root);
|
|
31361
|
+
await chmod(script, 493).catch(() => void 0);
|
|
31362
|
+
const kept = (await crontabList()).split("\n").filter((l) => l.trim() && !l.includes(script));
|
|
31363
|
+
kept.push(`${status.cronSchedule} ${script}`);
|
|
31364
|
+
await crontabSet(kept.join("\n"));
|
|
31365
|
+
}
|
|
31366
|
+
async function uninstallCron(root) {
|
|
31367
|
+
const script = tickScript(root);
|
|
31368
|
+
const cur = await crontabList();
|
|
31369
|
+
if (!cur.includes(script)) return;
|
|
31370
|
+
const kept = cur.split("\n").filter((l) => l.trim() && !l.includes(script));
|
|
31371
|
+
await crontabSet(kept.join("\n"));
|
|
31372
|
+
}
|
|
31373
|
+
async function writeAutonomous(root, req, by) {
|
|
31374
|
+
try {
|
|
31375
|
+
switch (req.kind) {
|
|
31376
|
+
case "settings": {
|
|
31377
|
+
if (!parseJson(req.settings)) return { ok: false, error: "settings is not valid JSON" };
|
|
31378
|
+
await writeFile2(join12(root, SETTINGS_REL), req.settings, "utf8");
|
|
31379
|
+
break;
|
|
31380
|
+
}
|
|
31381
|
+
case "approvals": {
|
|
31382
|
+
const map = parseJson(await readText(join12(root, APPROVALS_REL), "{}")) ?? {};
|
|
31383
|
+
if (req.approved) map[req.taskId] = { approved: true, by, at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
31384
|
+
else delete map[req.taskId];
|
|
31385
|
+
await writeFile2(join12(root, APPROVALS_REL), JSON.stringify(map, null, 2) + "\n", "utf8");
|
|
31386
|
+
break;
|
|
31387
|
+
}
|
|
31388
|
+
case "userTodo": {
|
|
31389
|
+
const cur = await readText(join12(root, BOOK_FILES.userTodo));
|
|
31390
|
+
const block = `
|
|
31391
|
+
|
|
31392
|
+
> posted by ${by} at ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
31393
|
+
${req.content.trim()}
|
|
31394
|
+
`;
|
|
31395
|
+
await writeFile2(join12(root, BOOK_FILES.userTodo), cur.trimEnd() + block, "utf8");
|
|
31396
|
+
break;
|
|
31397
|
+
}
|
|
31398
|
+
case "cron": {
|
|
31399
|
+
if (req.action === "install") await installCron(root);
|
|
31400
|
+
else await uninstallCron(root);
|
|
31401
|
+
break;
|
|
31402
|
+
}
|
|
31403
|
+
}
|
|
31404
|
+
return { ok: true, status: await getAutonomousStatus(root) };
|
|
31405
|
+
} catch (err) {
|
|
31406
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
31407
|
+
}
|
|
31408
|
+
}
|
|
31409
|
+
|
|
31410
|
+
// src/core/agents.ts
|
|
31411
|
+
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3, readdir as readdir3, rm } from "fs/promises";
|
|
31412
|
+
import { join as join13 } from "path";
|
|
31413
|
+
var AGENTS_REL = ".claude/agents";
|
|
31414
|
+
var SKILLS_REL = ".claude/skills";
|
|
31415
|
+
var MEMORY_DIRS = /* @__PURE__ */ new Set(["short-memory", "long-memory"]);
|
|
31416
|
+
function safeName(name) {
|
|
31417
|
+
return name.replace(/[^\w.-]+/g, "-").replace(/^[-.]+|[-.]+$/g, "");
|
|
31418
|
+
}
|
|
31419
|
+
async function readText2(path, fallback = "") {
|
|
31420
|
+
try {
|
|
31421
|
+
return await readFile3(path, "utf8");
|
|
31422
|
+
} catch {
|
|
31423
|
+
return fallback;
|
|
31424
|
+
}
|
|
31425
|
+
}
|
|
31426
|
+
function frontmatter(text) {
|
|
31427
|
+
const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
31428
|
+
const body = m?.[1] ?? "";
|
|
31429
|
+
const get = (key) => body.match(new RegExp(`^${key}:\\s*(.+)$`, "m"))?.[1]?.trim().replace(/^["']|["']$/g, "") ?? "";
|
|
31430
|
+
return { name: get("name"), description: get("description") };
|
|
31431
|
+
}
|
|
31432
|
+
async function listAgents(root) {
|
|
31433
|
+
const subagents = [];
|
|
31434
|
+
const skills = [];
|
|
31435
|
+
try {
|
|
31436
|
+
for (const e of await readdir3(join13(root, AGENTS_REL), { withFileTypes: true })) {
|
|
31437
|
+
if (!e.isFile() || !e.name.endsWith(".md") || MEMORY_DIRS.has(e.name)) continue;
|
|
31438
|
+
const fm = frontmatter(await readText2(join13(root, AGENTS_REL, e.name)));
|
|
31439
|
+
subagents.push({ name: e.name.replace(/\.md$/, ""), description: fm.description });
|
|
31440
|
+
}
|
|
31441
|
+
} catch {
|
|
31442
|
+
}
|
|
31443
|
+
try {
|
|
31444
|
+
for (const e of await readdir3(join13(root, SKILLS_REL), { withFileTypes: true })) {
|
|
31445
|
+
if (!e.isDirectory()) continue;
|
|
31446
|
+
const fm = frontmatter(await readText2(join13(root, SKILLS_REL, e.name, "SKILL.md")));
|
|
31447
|
+
skills.push({ name: e.name, description: fm.description });
|
|
31448
|
+
}
|
|
31449
|
+
} catch {
|
|
31450
|
+
}
|
|
31451
|
+
subagents.sort((a, b) => a.name.localeCompare(b.name));
|
|
31452
|
+
skills.sort((a, b) => a.name.localeCompare(b.name));
|
|
31453
|
+
return { subagents, skills };
|
|
31454
|
+
}
|
|
31455
|
+
function itemPath(root, kind, name) {
|
|
31456
|
+
const n = safeName(name);
|
|
31457
|
+
return kind === "subagent" ? join13(root, AGENTS_REL, `${n}.md`) : join13(root, SKILLS_REL, n, "SKILL.md");
|
|
31458
|
+
}
|
|
31459
|
+
async function readAgent(root, kind, name) {
|
|
31460
|
+
const content = await readText2(itemPath(root, kind, name));
|
|
31461
|
+
if (kind !== "subagent") return { content };
|
|
31462
|
+
const n = safeName(name);
|
|
31463
|
+
const [short, long] = await Promise.all([
|
|
31464
|
+
readText2(join13(root, AGENTS_REL, "short-memory", `${n}.md`)),
|
|
31465
|
+
readText2(join13(root, AGENTS_REL, "long-memory", `${n}.md`))
|
|
31466
|
+
]);
|
|
31467
|
+
return { content, memory: { short, long } };
|
|
31468
|
+
}
|
|
31469
|
+
async function writeAgent(root, req) {
|
|
31470
|
+
const n = safeName(req.name);
|
|
31471
|
+
if (!n) return { ok: false, error: "invalid name" };
|
|
31472
|
+
try {
|
|
31473
|
+
if (req.action === "delete") {
|
|
31474
|
+
if (req.kind === "subagent") await rm(itemPath(root, "subagent", n), { force: true });
|
|
31475
|
+
else await rm(join13(root, SKILLS_REL, n), { recursive: true, force: true });
|
|
31476
|
+
return { ok: true };
|
|
31477
|
+
}
|
|
31478
|
+
if (req.content === void 0) return { ok: false, error: "content is required" };
|
|
31479
|
+
const path = itemPath(root, req.kind, n);
|
|
31480
|
+
await mkdir3(join13(path, ".."), { recursive: true });
|
|
31481
|
+
await writeFile3(path, req.content, "utf8");
|
|
31482
|
+
return { ok: true };
|
|
31483
|
+
} catch (err) {
|
|
31484
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
31485
|
+
}
|
|
31486
|
+
}
|
|
31487
|
+
|
|
31488
|
+
// src/core/secrets.ts
|
|
31489
|
+
import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
31490
|
+
import { join as join14 } from "path";
|
|
31491
|
+
var SECURITY_REL = "AI_SECURITY.md";
|
|
31492
|
+
var PLACEHOLDER_REL = "AI_PLACEHOLDER.md";
|
|
31493
|
+
var SECRETS_REL = "project.secrets.json";
|
|
31494
|
+
async function readText3(path, fallback = "") {
|
|
31495
|
+
try {
|
|
31496
|
+
return await readFile4(path, "utf8");
|
|
31497
|
+
} catch {
|
|
31498
|
+
return fallback;
|
|
31499
|
+
}
|
|
31500
|
+
}
|
|
31501
|
+
async function readSecretsObject(root) {
|
|
31502
|
+
try {
|
|
31503
|
+
const v = JSON.parse(await readFile4(join14(root, SECRETS_REL), "utf8"));
|
|
31504
|
+
return v && typeof v === "object" ? v : {};
|
|
31505
|
+
} catch {
|
|
31506
|
+
return {};
|
|
31507
|
+
}
|
|
31508
|
+
}
|
|
31509
|
+
function placeholderKeys(text) {
|
|
31510
|
+
const keys = [];
|
|
31511
|
+
for (const line of text.split("\n")) {
|
|
31512
|
+
const s = line.trim();
|
|
31513
|
+
if (!s.startsWith("|")) continue;
|
|
31514
|
+
if (/^\|[\s:|-]+\|?$/.test(s)) continue;
|
|
31515
|
+
const first = s.replace(/^\|/, "").split("|")[0]?.trim() ?? "";
|
|
31516
|
+
if (/^[\w.-]+$/.test(first) && first.toLowerCase() !== "key") keys.push(first);
|
|
31517
|
+
}
|
|
31518
|
+
return keys;
|
|
31519
|
+
}
|
|
31520
|
+
async function readSecrets(root) {
|
|
31521
|
+
const [security, placeholder, secrets] = await Promise.all([
|
|
31522
|
+
readText3(join14(root, SECURITY_REL)),
|
|
31523
|
+
readText3(join14(root, PLACEHOLDER_REL)),
|
|
31524
|
+
readSecretsObject(root)
|
|
31525
|
+
]);
|
|
31526
|
+
const names = /* @__PURE__ */ new Set([...placeholderKeys(placeholder), ...Object.keys(secrets)]);
|
|
31527
|
+
const keys = [...names].sort().map((name) => ({
|
|
31528
|
+
name,
|
|
31529
|
+
set: secrets[name] !== void 0 && String(secrets[name]).length > 0
|
|
31530
|
+
}));
|
|
31531
|
+
return { security, placeholder, keys };
|
|
31532
|
+
}
|
|
31533
|
+
async function writeSecrets(root, req) {
|
|
31534
|
+
try {
|
|
31535
|
+
if (req.kind === "security") {
|
|
31536
|
+
await writeFile4(join14(root, SECURITY_REL), req.content, "utf8");
|
|
31537
|
+
} else if (req.kind === "placeholder") {
|
|
31538
|
+
await writeFile4(join14(root, PLACEHOLDER_REL), req.content, "utf8");
|
|
31539
|
+
} else {
|
|
31540
|
+
const secrets = await readSecretsObject(root);
|
|
31541
|
+
if (req.kind === "secret") secrets[req.key] = req.value;
|
|
31542
|
+
else delete secrets[req.key];
|
|
31543
|
+
await writeFile4(join14(root, SECRETS_REL), JSON.stringify(secrets, null, 2) + "\n", "utf8");
|
|
31544
|
+
}
|
|
31545
|
+
return { ok: true };
|
|
31546
|
+
} catch (err) {
|
|
31547
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
31548
|
+
}
|
|
31549
|
+
}
|
|
31550
|
+
|
|
31551
|
+
// src/core/graph.ts
|
|
31552
|
+
import { readFile as readFile5, readdir as readdir4 } from "fs/promises";
|
|
31553
|
+
import { join as join15, relative, dirname as dirname4, resolve as resolve4, extname, basename as basename2 } from "path";
|
|
31554
|
+
var MAX_FILES = 2e3;
|
|
31555
|
+
var MAX_NODES = 3e3;
|
|
31556
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "out", "coverage", ".turbo"]);
|
|
31557
|
+
var CODE_EXT = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".java", ".rb", ".php"]);
|
|
31558
|
+
async function walk(root, accept) {
|
|
31559
|
+
const out = [];
|
|
31560
|
+
const stack = [root];
|
|
31561
|
+
while (stack.length && out.length < MAX_FILES) {
|
|
31562
|
+
const dir2 = stack.pop();
|
|
31563
|
+
let entries2;
|
|
31564
|
+
try {
|
|
31565
|
+
entries2 = await readdir4(dir2, { withFileTypes: true });
|
|
31566
|
+
} catch {
|
|
31567
|
+
continue;
|
|
31568
|
+
}
|
|
31569
|
+
for (const e of entries2) {
|
|
31570
|
+
if (e.name.startsWith(".") && e.name !== ".claude") {
|
|
31571
|
+
if (e.isDirectory()) continue;
|
|
31572
|
+
}
|
|
31573
|
+
const full = join15(dir2, e.name);
|
|
31574
|
+
if (e.isDirectory()) {
|
|
31575
|
+
if (!SKIP_DIRS.has(e.name)) stack.push(full);
|
|
31576
|
+
} else if (accept(full)) {
|
|
31577
|
+
out.push(full);
|
|
31578
|
+
if (out.length >= MAX_FILES) break;
|
|
31579
|
+
}
|
|
31580
|
+
}
|
|
31581
|
+
}
|
|
31582
|
+
return out;
|
|
31583
|
+
}
|
|
31584
|
+
function orphansOf(nodes, edges) {
|
|
31585
|
+
const hasInbound = new Set(edges.map((e) => e.to));
|
|
31586
|
+
return nodes.filter((n) => !hasInbound.has(n.id)).map((n) => n.id);
|
|
31587
|
+
}
|
|
31588
|
+
async function buildDocs(root) {
|
|
31589
|
+
const files = await walk(root, (p) => extname(p) === ".md");
|
|
31590
|
+
const ids = new Set(files.map((f) => relative(root, f).split("\\").join("/")));
|
|
31591
|
+
const nodes = files.map((f) => {
|
|
31592
|
+
const rel = relative(root, f).split("\\").join("/");
|
|
31593
|
+
return { id: rel, label: basename2(rel), kind: "doc", file: rel };
|
|
31594
|
+
});
|
|
31595
|
+
const edges = [];
|
|
31596
|
+
const seen = /* @__PURE__ */ new Set();
|
|
31597
|
+
for (const f of files) {
|
|
31598
|
+
const rel = relative(root, f).split("\\").join("/");
|
|
31599
|
+
let text = "";
|
|
31600
|
+
try {
|
|
31601
|
+
text = await readFile5(f, "utf8");
|
|
31602
|
+
} catch {
|
|
31603
|
+
continue;
|
|
31604
|
+
}
|
|
31605
|
+
for (const m of text.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)) {
|
|
31606
|
+
const href = (m[1] ?? "").split("#")[0].trim();
|
|
31607
|
+
if (!href || !href.endsWith(".md") || href.startsWith("http")) continue;
|
|
31608
|
+
const target = relative(root, resolve4(dirname4(f), href)).split("\\").join("/");
|
|
31609
|
+
if (ids.has(target) && target !== rel) {
|
|
31610
|
+
const key = `${rel}->${target}`;
|
|
31611
|
+
if (!seen.has(key)) {
|
|
31612
|
+
seen.add(key);
|
|
31613
|
+
edges.push({ from: rel, to: target });
|
|
31614
|
+
}
|
|
31615
|
+
}
|
|
31616
|
+
}
|
|
31617
|
+
}
|
|
31618
|
+
return { nodes, edges, orphans: orphansOf(nodes, edges) };
|
|
31619
|
+
}
|
|
31620
|
+
var DEF_PATTERNS = [
|
|
31621
|
+
/\bfunction\s+([A-Za-z_$][\w$]*)/g,
|
|
31622
|
+
// JS/TS function decl
|
|
31623
|
+
/\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?(?:function\b|\([^)]*\)\s*=>|[A-Za-z_$][\w$]*\s*=>)/g,
|
|
31624
|
+
// arrow/func expr
|
|
31625
|
+
/^\s*def\s+([A-Za-z_]\w*)/gm,
|
|
31626
|
+
// Python
|
|
31627
|
+
/\bfunc\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)/g
|
|
31628
|
+
// Go
|
|
31629
|
+
];
|
|
31630
|
+
async function buildCode(root) {
|
|
31631
|
+
const files = await walk(root, (p) => CODE_EXT.has(extname(p)));
|
|
31632
|
+
const nodes = [];
|
|
31633
|
+
const nameToIds = /* @__PURE__ */ new Map();
|
|
31634
|
+
const fileDefs = /* @__PURE__ */ new Map();
|
|
31635
|
+
for (const f of files) {
|
|
31636
|
+
if (nodes.length >= MAX_NODES) break;
|
|
31637
|
+
const rel = relative(root, f).split("\\").join("/");
|
|
31638
|
+
let text = "";
|
|
31639
|
+
try {
|
|
31640
|
+
text = await readFile5(f, "utf8");
|
|
31641
|
+
} catch {
|
|
31642
|
+
continue;
|
|
31643
|
+
}
|
|
31644
|
+
const defs = [];
|
|
31645
|
+
for (const re of DEF_PATTERNS) {
|
|
31646
|
+
for (const m of text.matchAll(re)) {
|
|
31647
|
+
const name = m[1] ?? "";
|
|
31648
|
+
if (!name || name.length < 2) continue;
|
|
31649
|
+
const id = `${rel}#${name}`;
|
|
31650
|
+
if (nameToIds.has(name) && (nameToIds.get(name) ?? []).includes(id)) continue;
|
|
31651
|
+
if (nodes.length >= MAX_NODES) break;
|
|
31652
|
+
defs.push({ name, id, start: m.index ?? 0 });
|
|
31653
|
+
nodes.push({ id, label: name, kind: "fn", file: rel });
|
|
31654
|
+
nameToIds.set(name, [...nameToIds.get(name) ?? [], id]);
|
|
31655
|
+
}
|
|
31656
|
+
}
|
|
31657
|
+
defs.sort((a, b) => a.start - b.start);
|
|
31658
|
+
fileDefs.set(rel, { rel, text, defs });
|
|
31659
|
+
}
|
|
31660
|
+
const edges = [];
|
|
31661
|
+
const seen = /* @__PURE__ */ new Set();
|
|
31662
|
+
for (const { text, defs } of fileDefs.values()) {
|
|
31663
|
+
for (let i = 0; i < defs.length; i++) {
|
|
31664
|
+
const def = defs[i];
|
|
31665
|
+
const end = i + 1 < defs.length ? defs[i + 1].start : text.length;
|
|
31666
|
+
const body = text.slice(def.start, end);
|
|
31667
|
+
for (const m of body.matchAll(/([A-Za-z_$][\w$]*)\s*\(/g)) {
|
|
31668
|
+
const callee = m[1] ?? "";
|
|
31669
|
+
if (callee === def.name) continue;
|
|
31670
|
+
for (const targetId of nameToIds.get(callee) ?? []) {
|
|
31671
|
+
if (targetId === def.id) continue;
|
|
31672
|
+
const key = `${def.id}->${targetId}`;
|
|
31673
|
+
if (!seen.has(key)) {
|
|
31674
|
+
seen.add(key);
|
|
31675
|
+
edges.push({ from: def.id, to: targetId });
|
|
31676
|
+
}
|
|
31677
|
+
}
|
|
31678
|
+
}
|
|
31679
|
+
}
|
|
31680
|
+
}
|
|
31681
|
+
return { nodes, edges, orphans: orphansOf(nodes, edges) };
|
|
31682
|
+
}
|
|
31683
|
+
async function buildGraph(root, mode) {
|
|
31684
|
+
try {
|
|
31685
|
+
return mode === "docs" ? await buildDocs(root) : await buildCode(root);
|
|
31686
|
+
} catch {
|
|
31687
|
+
return { nodes: [], edges: [], orphans: [] };
|
|
31688
|
+
}
|
|
31689
|
+
}
|
|
31690
|
+
|
|
31691
|
+
// src/core/rag.ts
|
|
31692
|
+
import { readFile as readFile6, writeFile as writeFile5, statfs, mkdir as mkdir4 } from "fs/promises";
|
|
31693
|
+
import { existsSync as existsSync7 } from "fs";
|
|
31694
|
+
import { execFile as execFile3, spawn as spawn5 } from "child_process";
|
|
31695
|
+
import { totalmem, cpus } from "os";
|
|
31696
|
+
import { join as join16 } from "path";
|
|
31697
|
+
import { promisify as promisify3 } from "util";
|
|
31698
|
+
var run3 = promisify3(execFile3);
|
|
31699
|
+
var RAG_REL = ".claude/rag";
|
|
31700
|
+
async function tryRun(cmd, args) {
|
|
31701
|
+
try {
|
|
31702
|
+
const { stdout } = await run3(cmd, args, { timeout: 8e3 });
|
|
31703
|
+
return { ok: true, out: stdout.trim() };
|
|
31704
|
+
} catch {
|
|
31705
|
+
return { ok: false, out: "" };
|
|
31706
|
+
}
|
|
31707
|
+
}
|
|
31708
|
+
async function detectPython() {
|
|
31709
|
+
for (const bin of ["python3", "python"]) {
|
|
31710
|
+
const r = await tryRun(bin, ["--version"]);
|
|
31711
|
+
if (r.ok) return { bin, found: true, version: r.out.replace(/^Python\s*/i, "") };
|
|
31712
|
+
}
|
|
31713
|
+
return { bin: "python3", found: false, version: "" };
|
|
31714
|
+
}
|
|
31715
|
+
async function installLog(root) {
|
|
31716
|
+
try {
|
|
31717
|
+
const text = await readFile6(join16(root, RAG_REL, "install.log"), "utf8");
|
|
31718
|
+
return text.split("\n").filter((l) => l.length > 0).slice(-200);
|
|
31719
|
+
} catch {
|
|
31720
|
+
return [];
|
|
31721
|
+
}
|
|
31722
|
+
}
|
|
31723
|
+
function optionsFor(m) {
|
|
31724
|
+
const opts = [
|
|
31725
|
+
{
|
|
31726
|
+
id: "small",
|
|
31727
|
+
label: "Small (bge-small, EN) \xB7 ~130 MB",
|
|
31728
|
+
model: "BAAI/bge-small-en-v1.5",
|
|
31729
|
+
sizeMB: 130,
|
|
31730
|
+
recommended: m.ramGB < 8,
|
|
31731
|
+
note: "Lightweight, low RAM."
|
|
31732
|
+
}
|
|
31733
|
+
];
|
|
31734
|
+
if (m.diskFreeGB > 1.5) {
|
|
31735
|
+
opts.push({
|
|
31736
|
+
id: "multilingual",
|
|
31737
|
+
label: "Multilingual (MiniLM, incl. Vietnamese) \xB7 ~470 MB",
|
|
31738
|
+
model: "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
|
|
31739
|
+
sizeMB: 470,
|
|
31740
|
+
recommended: m.ramGB >= 8 && m.diskFreeGB > 2,
|
|
31741
|
+
note: "Best for mixed VI/EN content."
|
|
31742
|
+
});
|
|
31743
|
+
}
|
|
31744
|
+
if (m.ramGB >= 8 && m.diskFreeGB > 2) {
|
|
31745
|
+
opts.push({
|
|
31746
|
+
id: "base",
|
|
31747
|
+
label: "Base (bge-base, EN) \xB7 ~440 MB",
|
|
31748
|
+
model: "BAAI/bge-base-en-v1.5",
|
|
31749
|
+
sizeMB: 440,
|
|
31750
|
+
recommended: false,
|
|
31751
|
+
note: "Higher quality EN, needs \u2265 8 GB RAM."
|
|
31752
|
+
});
|
|
31753
|
+
}
|
|
31754
|
+
return opts;
|
|
31755
|
+
}
|
|
31756
|
+
async function ragStatus(root) {
|
|
31757
|
+
const py = await detectPython();
|
|
31758
|
+
const [pip, fastembed, sqliteVec] = py.found ? await Promise.all([
|
|
31759
|
+
tryRun(py.bin, ["-m", "pip", "--version"]).then((r) => r.ok),
|
|
31760
|
+
tryRun(py.bin, ["-c", "import fastembed"]).then((r) => r.ok),
|
|
31761
|
+
tryRun(py.bin, ["-c", "import sqlite_vec"]).then((r) => r.ok)
|
|
31762
|
+
]) : [false, false, false];
|
|
31763
|
+
let diskFreeGB = 0;
|
|
31764
|
+
try {
|
|
31765
|
+
const s = await statfs(root);
|
|
31766
|
+
diskFreeGB = Math.round(s.bavail * s.bsize / 1e9 * 10) / 10;
|
|
31767
|
+
} catch {
|
|
31768
|
+
diskFreeGB = 0;
|
|
31769
|
+
}
|
|
31770
|
+
const gpu = (await tryRun("nvidia-smi", ["-L"])).ok;
|
|
31771
|
+
const machine = {
|
|
31772
|
+
ramGB: Math.round(totalmem() / 1e9 * 10) / 10,
|
|
31773
|
+
cpus: cpus().length,
|
|
31774
|
+
diskFreeGB,
|
|
31775
|
+
gpu
|
|
31776
|
+
};
|
|
31777
|
+
let modelName = "";
|
|
31778
|
+
try {
|
|
31779
|
+
modelName = (await readFile6(join16(root, RAG_REL, ".installed"), "utf8")).trim();
|
|
31780
|
+
} catch {
|
|
31781
|
+
modelName = "";
|
|
31782
|
+
}
|
|
31783
|
+
const modelPresent = modelName.length > 0;
|
|
31784
|
+
return {
|
|
31785
|
+
installed: fastembed && sqliteVec && modelPresent,
|
|
31786
|
+
installing: existsSync7(join16(root, RAG_REL, ".installing")),
|
|
31787
|
+
python: { found: py.found, version: py.version },
|
|
31788
|
+
deps: { pip, fastembed, sqliteVec },
|
|
31789
|
+
model: { present: modelPresent, name: modelName },
|
|
31790
|
+
machine,
|
|
31791
|
+
options: optionsFor(machine),
|
|
31792
|
+
installLog: await installLog(root),
|
|
31793
|
+
index: await readIndexMeta(root)
|
|
31794
|
+
};
|
|
31795
|
+
}
|
|
31796
|
+
async function readIndexMeta(root) {
|
|
31797
|
+
const indexing = existsSync7(join16(root, RAG_REL, ".indexing"));
|
|
31798
|
+
try {
|
|
31799
|
+
const m = JSON.parse(await readFile6(join16(root, RAG_REL, ".index.json"), "utf8"));
|
|
31800
|
+
return {
|
|
31801
|
+
present: true,
|
|
31802
|
+
indexing,
|
|
31803
|
+
chunks: typeof m.chunks === "number" ? m.chunks : 0,
|
|
31804
|
+
indexedAt: typeof m.indexedAt === "string" ? m.indexedAt : ""
|
|
31805
|
+
};
|
|
31806
|
+
} catch {
|
|
31807
|
+
return { present: false, indexing, chunks: 0, indexedAt: "" };
|
|
31808
|
+
}
|
|
31809
|
+
}
|
|
31810
|
+
var RAG_ENGINE_PY = `import sys, os, json, glob, sqlite3, datetime
|
|
31811
|
+
BASE = os.path.join(".claude", "rag")
|
|
31812
|
+
DB = os.path.join(BASE, "index.db")
|
|
31813
|
+
def model_name():
|
|
31814
|
+
return open(os.path.join(BASE, ".installed"), encoding="utf-8").read().strip()
|
|
31815
|
+
def connect():
|
|
31816
|
+
import sqlite_vec
|
|
31817
|
+
db = sqlite3.connect(DB)
|
|
31818
|
+
db.enable_load_extension(True)
|
|
31819
|
+
sqlite_vec.load(db)
|
|
31820
|
+
db.enable_load_extension(False)
|
|
31821
|
+
return db
|
|
31822
|
+
def embedder():
|
|
31823
|
+
from fastembed import TextEmbedding
|
|
31824
|
+
return TextEmbedding(model_name())
|
|
31825
|
+
def reindex():
|
|
31826
|
+
import sqlite_vec
|
|
31827
|
+
emb = embedder()
|
|
31828
|
+
chunks = []
|
|
31829
|
+
for p in glob.glob("**/*.md", recursive=True):
|
|
31830
|
+
parts = p.split(os.sep)
|
|
31831
|
+
if "node_modules" in parts or ".git" in parts: continue
|
|
31832
|
+
try: txt = open(p, encoding="utf-8").read()
|
|
31833
|
+
except Exception: continue
|
|
31834
|
+
for i in range(0, len(txt), 800):
|
|
31835
|
+
c = txt[i:i+1000].strip()
|
|
31836
|
+
if len(c) > 20: chunks.append((p, c))
|
|
31837
|
+
if len(chunks) >= 5000: break
|
|
31838
|
+
vectors = list(emb.embed([c for _, c in chunks])) if chunks else []
|
|
31839
|
+
dim = len(vectors[0]) if vectors else 384
|
|
31840
|
+
os.makedirs(BASE, exist_ok=True)
|
|
31841
|
+
db = connect()
|
|
31842
|
+
db.execute("DROP TABLE IF EXISTS chunks")
|
|
31843
|
+
db.execute("CREATE VIRTUAL TABLE chunks USING vec0(embedding float[%d], +path text, +snippet text)" % dim)
|
|
31844
|
+
for (p, c), v in zip(chunks, vectors):
|
|
31845
|
+
db.execute("INSERT INTO chunks(embedding, path, snippet) VALUES (?, ?, ?)",
|
|
31846
|
+
(sqlite_vec.serialize_float32(list(v)), p, c[:400]))
|
|
31847
|
+
db.commit(); db.close()
|
|
31848
|
+
json.dump({"chunks": len(chunks), "indexedAt": datetime.datetime.now().isoformat(timespec="seconds"), "model": model_name()},
|
|
31849
|
+
open(os.path.join(BASE, ".index.json"), "w", encoding="utf-8"))
|
|
31850
|
+
print("[reindex] DONE %d chunks" % len(chunks))
|
|
31851
|
+
def query(q, k):
|
|
31852
|
+
import sqlite_vec
|
|
31853
|
+
v = list(embedder().embed([q]))[0]
|
|
31854
|
+
db = connect()
|
|
31855
|
+
rows = db.execute("SELECT path, snippet, distance FROM chunks WHERE embedding MATCH ? ORDER BY distance LIMIT ?",
|
|
31856
|
+
(sqlite_vec.serialize_float32(list(v)), k)).fetchall()
|
|
31857
|
+
db.close()
|
|
31858
|
+
out = [{"path": r[0], "snippet": r[1], "score": round(1.0/(1.0+float(r[2])), 4)} for r in rows]
|
|
31859
|
+
print(json.dumps({"results": out}, ensure_ascii=False))
|
|
31860
|
+
if __name__ == "__main__":
|
|
31861
|
+
cmd = sys.argv[1] if len(sys.argv) > 1 else ""
|
|
31862
|
+
if cmd == "reindex": reindex()
|
|
31863
|
+
elif cmd == "query": query(sys.argv[2], int(sys.argv[3]) if len(sys.argv) > 3 else 8)
|
|
31864
|
+
`;
|
|
31865
|
+
async function ensureEngine(root) {
|
|
31866
|
+
await mkdir4(join16(root, RAG_REL), { recursive: true }).catch(() => void 0);
|
|
31867
|
+
await writeFile5(join16(root, RAG_REL, "rag_engine.py"), RAG_ENGINE_PY, "utf8");
|
|
31868
|
+
}
|
|
31869
|
+
async function ragReindex(root) {
|
|
31870
|
+
const py = await detectPython();
|
|
31871
|
+
if (!py.found) return { ok: false, started: false, error: "python3 not found" };
|
|
31872
|
+
if (!existsSync7(join16(root, RAG_REL, ".installed"))) return { ok: false, started: false, error: "RAG is not installed" };
|
|
31873
|
+
if (existsSync7(join16(root, RAG_REL, ".indexing"))) return { ok: true, started: true };
|
|
31874
|
+
await ensureEngine(root);
|
|
31875
|
+
const script = `
|
|
31876
|
+
set +e
|
|
31877
|
+
touch "${RAG_REL}/.indexing"
|
|
31878
|
+
{ echo "[reindex] $(date)"; "$RAG_PY" "${RAG_REL}/rag_engine.py" reindex 2>&1; } >> "${RAG_REL}/index.log" 2>&1
|
|
31879
|
+
rm -f "${RAG_REL}/.indexing"
|
|
31880
|
+
`;
|
|
31881
|
+
const child = spawn5("bash", ["-c", script], {
|
|
31882
|
+
cwd: root,
|
|
31883
|
+
env: { ...process.env, RAG_PY: py.bin },
|
|
31884
|
+
detached: true,
|
|
31885
|
+
stdio: "ignore"
|
|
31886
|
+
});
|
|
31887
|
+
child.unref();
|
|
31888
|
+
return { ok: true, started: true };
|
|
31889
|
+
}
|
|
31890
|
+
async function ragQuery(root, queryText, k = 8) {
|
|
31891
|
+
const py = await detectPython();
|
|
31892
|
+
if (!py.found) return { results: [], error: "python3 not found" };
|
|
31893
|
+
if (!existsSync7(join16(root, RAG_REL, "index.db"))) return { results: [], error: "no index \u2014 reindex first" };
|
|
31894
|
+
await ensureEngine(root);
|
|
31895
|
+
try {
|
|
31896
|
+
const { stdout } = await run3(
|
|
31897
|
+
py.bin,
|
|
31898
|
+
[join16(RAG_REL, "rag_engine.py"), "query", queryText, String(Math.min(Math.max(k, 1), 20))],
|
|
31899
|
+
{ cwd: root, timeout: 3e4, maxBuffer: 4 * 1024 * 1024 }
|
|
31900
|
+
);
|
|
31901
|
+
const parsed = JSON.parse(stdout.trim() || '{"results":[]}');
|
|
31902
|
+
return { results: Array.isArray(parsed.results) ? parsed.results : [] };
|
|
31903
|
+
} catch (err) {
|
|
31904
|
+
return { results: [], error: err instanceof Error ? err.message : String(err) };
|
|
31905
|
+
}
|
|
31906
|
+
}
|
|
31907
|
+
async function ragInstall(root, model) {
|
|
31908
|
+
const py = await detectPython();
|
|
31909
|
+
if (!py.found) return { ok: false, started: false, error: "python3 not found on the worker" };
|
|
31910
|
+
if (existsSync7(join16(root, RAG_REL, ".installing"))) return { ok: true, started: true };
|
|
31911
|
+
try {
|
|
31912
|
+
await mkdir4(join16(root, RAG_REL), { recursive: true });
|
|
31913
|
+
} catch {
|
|
31914
|
+
}
|
|
31915
|
+
const script = `
|
|
31916
|
+
set +e
|
|
31917
|
+
touch "${RAG_REL}/.installing"
|
|
31918
|
+
rm -f "${RAG_REL}/.installed"
|
|
31919
|
+
{
|
|
31920
|
+
echo "[install] $(date) pip install fastembed sqlite-vec"
|
|
31921
|
+
"$RAG_PY" -m pip install --quiet --user fastembed sqlite-vec 2>&1
|
|
31922
|
+
echo "[install] warming model: $RAG_MODEL (first run downloads it)"
|
|
31923
|
+
"$RAG_PY" -c "from fastembed import TextEmbedding; TextEmbedding('$RAG_MODEL'); print('[install] model ready')" 2>&1
|
|
31924
|
+
if [ $? -eq 0 ]; then printf '%s' "$RAG_MODEL" > "${RAG_REL}/.installed"; echo "[install] DONE"; else echo "[install] FAILED"; fi
|
|
31925
|
+
} >> "${RAG_REL}/install.log" 2>&1
|
|
31926
|
+
rm -f "${RAG_REL}/.installing"
|
|
31927
|
+
`;
|
|
31928
|
+
const child = spawn5("bash", ["-c", script], {
|
|
31929
|
+
cwd: root,
|
|
31930
|
+
env: { ...process.env, RAG_PY: py.bin, RAG_MODEL: model },
|
|
31931
|
+
detached: true,
|
|
31932
|
+
stdio: "ignore"
|
|
31933
|
+
});
|
|
31934
|
+
child.unref();
|
|
31935
|
+
return { ok: true, started: true };
|
|
31936
|
+
}
|
|
31937
|
+
|
|
31938
|
+
// src/core/git-diff.ts
|
|
31939
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
31940
|
+
import { execFile as execFile4 } from "child_process";
|
|
31941
|
+
import { basename as basename3, dirname as dirname5, resolve as resolve5 } from "path";
|
|
31942
|
+
import { promisify as promisify4 } from "util";
|
|
31943
|
+
var run4 = promisify4(execFile4);
|
|
31078
31944
|
async function gitDiff(path) {
|
|
31079
|
-
const target =
|
|
31080
|
-
const dir2 =
|
|
31081
|
-
const base =
|
|
31945
|
+
const target = resolve5(path);
|
|
31946
|
+
const dir2 = dirname5(target);
|
|
31947
|
+
const base = basename3(target);
|
|
31082
31948
|
let newContent = "";
|
|
31083
31949
|
try {
|
|
31084
|
-
newContent = await
|
|
31950
|
+
newContent = await readFile7(target, "utf8");
|
|
31085
31951
|
} catch {
|
|
31086
31952
|
}
|
|
31087
31953
|
let oldContent = "";
|
|
31088
31954
|
try {
|
|
31089
|
-
const { stdout } = await
|
|
31955
|
+
const { stdout } = await run4("git", ["show", `HEAD:./${base}`], {
|
|
31090
31956
|
cwd: dir2,
|
|
31091
31957
|
timeout: 15e3,
|
|
31092
31958
|
maxBuffer: 4 * 1024 * 1024
|
|
@@ -31098,12 +31964,12 @@ async function gitDiff(path) {
|
|
|
31098
31964
|
}
|
|
31099
31965
|
|
|
31100
31966
|
// src/core/git-env.ts
|
|
31101
|
-
import { execFile as
|
|
31102
|
-
import { promisify as
|
|
31103
|
-
var
|
|
31967
|
+
import { execFile as execFile5 } from "child_process";
|
|
31968
|
+
import { promisify as promisify5 } from "util";
|
|
31969
|
+
var run5 = promisify5(execFile5);
|
|
31104
31970
|
async function hasBinary(bin, args = ["--version"]) {
|
|
31105
31971
|
try {
|
|
31106
|
-
await
|
|
31972
|
+
await run5(bin, args, { timeout: 5e3 });
|
|
31107
31973
|
return true;
|
|
31108
31974
|
} catch {
|
|
31109
31975
|
return false;
|
|
@@ -31116,7 +31982,7 @@ async function checkGitEnv(provider) {
|
|
|
31116
31982
|
let account = null;
|
|
31117
31983
|
if (installed) {
|
|
31118
31984
|
try {
|
|
31119
|
-
const { stdout, stderr } = await
|
|
31985
|
+
const { stdout, stderr } = await run5(bin, ["auth", "status"], { timeout: 8e3 });
|
|
31120
31986
|
const out = `${stdout}
|
|
31121
31987
|
${stderr}`;
|
|
31122
31988
|
authenticated = /logged in|logged into|✓/i.test(out);
|
|
@@ -31129,22 +31995,22 @@ ${stderr}`;
|
|
|
31129
31995
|
}
|
|
31130
31996
|
|
|
31131
31997
|
// src/core/git-history.ts
|
|
31132
|
-
import { readdir as
|
|
31133
|
-
import { execFile as
|
|
31134
|
-
import { resolve as
|
|
31135
|
-
import { promisify as
|
|
31136
|
-
var
|
|
31998
|
+
import { readdir as readdir5 } from "fs/promises";
|
|
31999
|
+
import { execFile as execFile6 } from "child_process";
|
|
32000
|
+
import { resolve as resolve6, sep as sep4 } from "path";
|
|
32001
|
+
import { promisify as promisify6 } from "util";
|
|
32002
|
+
var run6 = promisify6(execFile6);
|
|
31137
32003
|
var FS = "";
|
|
31138
32004
|
var RS = "";
|
|
31139
32005
|
function resolveRepo(root, repo) {
|
|
31140
32006
|
if (!root) return null;
|
|
31141
|
-
const base =
|
|
31142
|
-
const requested =
|
|
31143
|
-
return requested === base || requested.startsWith(base +
|
|
32007
|
+
const base = resolve6(root);
|
|
32008
|
+
const requested = resolve6(base, repo && repo.trim() ? repo : ".");
|
|
32009
|
+
return requested === base || requested.startsWith(base + sep4) ? requested : base;
|
|
31144
32010
|
}
|
|
31145
32011
|
async function git(cwd2, args) {
|
|
31146
32012
|
try {
|
|
31147
|
-
const { stdout } = await
|
|
32013
|
+
const { stdout } = await run6("git", args, {
|
|
31148
32014
|
cwd: cwd2,
|
|
31149
32015
|
timeout: 15e3,
|
|
31150
32016
|
maxBuffer: 8 * 1024 * 1024
|
|
@@ -31156,13 +32022,13 @@ async function git(cwd2, args) {
|
|
|
31156
32022
|
}
|
|
31157
32023
|
async function gitRepos(root) {
|
|
31158
32024
|
if (!root) return { repos: [] };
|
|
31159
|
-
const base =
|
|
32025
|
+
const base = resolve6(root);
|
|
31160
32026
|
const found = /* @__PURE__ */ new Set();
|
|
31161
32027
|
const repos = [];
|
|
31162
32028
|
const add = async (dir2) => {
|
|
31163
32029
|
const inside = await git(dir2, ["rev-parse", "--show-toplevel"]);
|
|
31164
32030
|
const top = inside.trim();
|
|
31165
|
-
if (!top || found.has(top) || !(top === base || top.startsWith(base +
|
|
32031
|
+
if (!top || found.has(top) || !(top === base || top.startsWith(base + sep4))) return;
|
|
31166
32032
|
found.add(top);
|
|
31167
32033
|
const subdir = top === base ? "" : top.slice(base.length + 1);
|
|
31168
32034
|
const remote = (await git(top, ["remote", "get-url", "origin"])).trim();
|
|
@@ -31170,16 +32036,16 @@ async function gitRepos(root) {
|
|
|
31170
32036
|
};
|
|
31171
32037
|
await add(base);
|
|
31172
32038
|
try {
|
|
31173
|
-
const lvl1 = await
|
|
32039
|
+
const lvl1 = await readdir5(base, { withFileTypes: true });
|
|
31174
32040
|
for (const d of lvl1) {
|
|
31175
32041
|
if (!d.isDirectory() || d.name === ".git" || d.name === "node_modules") continue;
|
|
31176
|
-
const p1 =
|
|
32042
|
+
const p1 = resolve6(base, d.name);
|
|
31177
32043
|
await add(p1);
|
|
31178
32044
|
try {
|
|
31179
|
-
const lvl2 = await
|
|
32045
|
+
const lvl2 = await readdir5(p1, { withFileTypes: true });
|
|
31180
32046
|
for (const d2 of lvl2) {
|
|
31181
32047
|
if (!d2.isDirectory() || d2.name === ".git" || d2.name === "node_modules") continue;
|
|
31182
|
-
await add(
|
|
32048
|
+
await add(resolve6(p1, d2.name));
|
|
31183
32049
|
}
|
|
31184
32050
|
} catch {
|
|
31185
32051
|
}
|
|
@@ -31246,24 +32112,24 @@ async function gitCommitDiff(root, repo, hash, path) {
|
|
|
31246
32112
|
}
|
|
31247
32113
|
|
|
31248
32114
|
// src/core/git-commit-identity.ts
|
|
31249
|
-
import { execFile as
|
|
31250
|
-
import { promisify as
|
|
31251
|
-
var
|
|
32115
|
+
import { execFile as execFile7 } from "child_process";
|
|
32116
|
+
import { promisify as promisify7 } from "util";
|
|
32117
|
+
var run7 = promisify7(execFile7);
|
|
31252
32118
|
async function setCommitAuthor(cwd2, machineUsername) {
|
|
31253
32119
|
if (!machineUsername) return;
|
|
31254
32120
|
try {
|
|
31255
|
-
await
|
|
32121
|
+
await run7("git", ["config", "--local", "user.name", machineUsername], { cwd: cwd2, timeout: 8e3 });
|
|
31256
32122
|
} catch {
|
|
31257
32123
|
}
|
|
31258
32124
|
}
|
|
31259
32125
|
|
|
31260
32126
|
// src/core/scaffold.ts
|
|
31261
|
-
import { cp, mkdir as
|
|
31262
|
-
import { execFile as
|
|
31263
|
-
import { dirname as
|
|
32127
|
+
import { cp, mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
|
|
32128
|
+
import { execFile as execFile8 } from "child_process";
|
|
32129
|
+
import { dirname as dirname6, join as join17, resolve as resolve7 } from "path";
|
|
31264
32130
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
31265
|
-
import { promisify as
|
|
31266
|
-
var
|
|
32131
|
+
import { promisify as promisify8 } from "util";
|
|
32132
|
+
var run8 = promisify8(execFile8);
|
|
31267
32133
|
function reposOf(spec) {
|
|
31268
32134
|
const repos = spec?.repos;
|
|
31269
32135
|
return Array.isArray(repos) ? repos : [];
|
|
@@ -31277,46 +32143,46 @@ async function provisionRepos(target, repos, emit, opts = {}) {
|
|
|
31277
32143
|
if (repo.primary) {
|
|
31278
32144
|
if (opts.clonePrimary && repo.op === "existing" && repo.url) {
|
|
31279
32145
|
emit("git", `Cloning ${repo.url}\u2026`);
|
|
31280
|
-
await
|
|
32146
|
+
await run8("git", ["clone", repo.url, target], { timeout: 12e4 });
|
|
31281
32147
|
continue;
|
|
31282
32148
|
}
|
|
31283
32149
|
emit("git", "Initializing the primary repo\u2026");
|
|
31284
|
-
await
|
|
32150
|
+
await run8("git", ["init"], { cwd: target, timeout: 2e4 });
|
|
31285
32151
|
if (repo.op === "existing" && repo.url) {
|
|
31286
|
-
await
|
|
32152
|
+
await run8("git", ["remote", "add", "origin", repo.url], { cwd: target, timeout: 2e4 }).catch(
|
|
31287
32153
|
() => void 0
|
|
31288
32154
|
);
|
|
31289
32155
|
}
|
|
31290
32156
|
continue;
|
|
31291
32157
|
}
|
|
31292
|
-
const dir2 =
|
|
32158
|
+
const dir2 = join17(target, subDirName(repo));
|
|
31293
32159
|
if (repo.op === "existing" && repo.url) {
|
|
31294
32160
|
emit("git", `Cloning sub-repo ${repo.url}\u2026`);
|
|
31295
|
-
await
|
|
32161
|
+
await run8("git", ["clone", repo.url, dir2], { timeout: 12e4 });
|
|
31296
32162
|
} else {
|
|
31297
32163
|
emit("git", `Initializing sub-repo ${subDirName(repo)}\u2026`);
|
|
31298
|
-
await
|
|
31299
|
-
await
|
|
32164
|
+
await mkdir5(dir2, { recursive: true });
|
|
32165
|
+
await run8("git", ["init"], { cwd: dir2, timeout: 2e4 });
|
|
31300
32166
|
}
|
|
31301
32167
|
}
|
|
31302
32168
|
}
|
|
31303
32169
|
function sampleDir() {
|
|
31304
32170
|
if (process.env.SCAFFOLD_SAMPLE_DIR) return process.env.SCAFFOLD_SAMPLE_DIR;
|
|
31305
|
-
const here =
|
|
31306
|
-
return
|
|
32171
|
+
const here = dirname6(fileURLToPath3(import.meta.url));
|
|
32172
|
+
return resolve7(here, "../../project-sample");
|
|
31307
32173
|
}
|
|
31308
32174
|
async function scaffoldProject(payload, profileDir2, onProgress) {
|
|
31309
32175
|
const emit = (step, message) => onProgress?.({ projectId: payload.projectId, step, message });
|
|
31310
32176
|
try {
|
|
31311
|
-
const root = process.env.SCAFFOLD_ROOT ?
|
|
31312
|
-
const target =
|
|
31313
|
-
await
|
|
32177
|
+
const root = process.env.SCAFFOLD_ROOT ? resolve7(process.env.SCAFFOLD_ROOT) : resolve7(profileDir2);
|
|
32178
|
+
const target = join17(root, payload.projectName);
|
|
32179
|
+
await mkdir5(target, { recursive: true });
|
|
31314
32180
|
emit("copy", "Copying the sample template\u2026");
|
|
31315
32181
|
await cp(sampleDir(), target, { recursive: true, force: false, errorOnExist: false });
|
|
31316
32182
|
if (payload.spec) {
|
|
31317
32183
|
emit("spec", "Writing project.spec.json\u2026");
|
|
31318
|
-
await
|
|
31319
|
-
|
|
32184
|
+
await writeFile6(
|
|
32185
|
+
join17(target, "project.spec.json"),
|
|
31320
32186
|
JSON.stringify(payload.spec, null, 2),
|
|
31321
32187
|
"utf8"
|
|
31322
32188
|
);
|
|
@@ -31334,7 +32200,7 @@ async function aiInit(target, spec, emit) {
|
|
|
31334
32200
|
emit("ai-init", "AI init: subagents, README, CLAUDE.md\u2026");
|
|
31335
32201
|
const subagents = Array.isArray(spec.subagents) ? spec.subagents : [];
|
|
31336
32202
|
if (subagents.length > 0) {
|
|
31337
|
-
await
|
|
32203
|
+
await mkdir5(join17(target, ".claude", "agents"), { recursive: true });
|
|
31338
32204
|
for (const sa of subagents) {
|
|
31339
32205
|
const name = (sa.name || "").trim().replace(/[^\w.-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
31340
32206
|
if (!name) continue;
|
|
@@ -31344,17 +32210,17 @@ name: ${name}
|
|
|
31344
32210
|
|
|
31345
32211
|
${(sa.description || "").trim()}
|
|
31346
32212
|
`;
|
|
31347
|
-
await
|
|
32213
|
+
await writeFile6(join17(target, ".claude", "agents", `${name}.md`), body, "utf8");
|
|
31348
32214
|
}
|
|
31349
32215
|
}
|
|
31350
32216
|
const specJson = JSON.stringify(spec);
|
|
31351
32217
|
await generateFile(
|
|
31352
|
-
|
|
32218
|
+
join17(target, "README.md"),
|
|
31353
32219
|
`Write a concise README.md (Markdown only, no preamble) for this project from its spec JSON:
|
|
31354
32220
|
${specJson}`
|
|
31355
32221
|
);
|
|
31356
32222
|
await generateFile(
|
|
31357
|
-
|
|
32223
|
+
join17(target, "CLAUDE.md"),
|
|
31358
32224
|
`Write a CLAUDE.md (Markdown only, no preamble) with guidance/conventions for AI agents working in this project, derived from its spec JSON:
|
|
31359
32225
|
${specJson}`
|
|
31360
32226
|
);
|
|
@@ -31362,16 +32228,16 @@ ${specJson}`
|
|
|
31362
32228
|
async function generateFile(path, prompt) {
|
|
31363
32229
|
try {
|
|
31364
32230
|
const text = await aiGenerate(prompt);
|
|
31365
|
-
if (text.trim()) await
|
|
32231
|
+
if (text.trim()) await writeFile6(path, text.trim() + "\n", "utf8");
|
|
31366
32232
|
} catch {
|
|
31367
32233
|
}
|
|
31368
32234
|
}
|
|
31369
32235
|
async function addProject(payload, profileDir2, onProgress) {
|
|
31370
32236
|
const emit = (step, message) => onProgress?.({ projectId: payload.projectId, step, message });
|
|
31371
32237
|
try {
|
|
31372
|
-
const root = process.env.SCAFFOLD_ROOT ?
|
|
31373
|
-
const target =
|
|
31374
|
-
await
|
|
32238
|
+
const root = process.env.SCAFFOLD_ROOT ? resolve7(process.env.SCAFFOLD_ROOT) : resolve7(profileDir2);
|
|
32239
|
+
const target = join17(root, payload.projectName);
|
|
32240
|
+
await mkdir5(target, { recursive: true });
|
|
31375
32241
|
const repos = payload.repos ?? [];
|
|
31376
32242
|
if (repos.length > 0) await provisionRepos(target, repos, emit, { clonePrimary: true });
|
|
31377
32243
|
onProgress?.({ projectId: payload.projectId, step: "done", message: "Project added.", done: true });
|
|
@@ -31381,6 +32247,40 @@ async function addProject(payload, profileDir2, onProgress) {
|
|
|
31381
32247
|
}
|
|
31382
32248
|
}
|
|
31383
32249
|
|
|
32250
|
+
// src/core/config-sync.ts
|
|
32251
|
+
import { existsSync as existsSync8, readFileSync as readFileSync13 } from "fs";
|
|
32252
|
+
import { join as join18 } from "path";
|
|
32253
|
+
var SERVER_MANAGED_KEYS = ["physicPath", "sessionSwitchPct", "perPromptTokenLimit"];
|
|
32254
|
+
function readConfigText(profileDir2) {
|
|
32255
|
+
const path = join18(profileDir2, "config.json");
|
|
32256
|
+
if (existsSync8(path)) {
|
|
32257
|
+
try {
|
|
32258
|
+
return readFileSync13(path, "utf8");
|
|
32259
|
+
} catch {
|
|
32260
|
+
}
|
|
32261
|
+
}
|
|
32262
|
+
return JSON.stringify(defaultProfileConfig(), null, 2);
|
|
32263
|
+
}
|
|
32264
|
+
function applyConfigText(profileDir2, text) {
|
|
32265
|
+
let parsed;
|
|
32266
|
+
try {
|
|
32267
|
+
parsed = JSON.parse(text);
|
|
32268
|
+
} catch (err) {
|
|
32269
|
+
return { ok: false, error: `Invalid JSON: ${err.message}` };
|
|
32270
|
+
}
|
|
32271
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
32272
|
+
return { ok: false, error: "config.json must be a JSON object." };
|
|
32273
|
+
}
|
|
32274
|
+
const next = { ...parsed };
|
|
32275
|
+
const current = readProfileConfig(profileDir2);
|
|
32276
|
+
for (const key of SERVER_MANAGED_KEYS) {
|
|
32277
|
+
if (current[key] !== void 0) next[key] = current[key];
|
|
32278
|
+
else delete next[key];
|
|
32279
|
+
}
|
|
32280
|
+
overwriteProfileConfig(profileDir2, next);
|
|
32281
|
+
return { ok: true };
|
|
32282
|
+
}
|
|
32283
|
+
|
|
31384
32284
|
// src/core/ws-client.ts
|
|
31385
32285
|
var HEARTBEAT_MS = 3e4;
|
|
31386
32286
|
var USAGE_POLL_MS = 5 * 6e4;
|
|
@@ -31397,6 +32297,11 @@ function envReconnectMaxSec() {
|
|
|
31397
32297
|
var MAX_HASHCODE_INVALID_RETRIES = 5;
|
|
31398
32298
|
var CLOSE_DRAINING = 4001;
|
|
31399
32299
|
var OFFLINE_BUFFER_MAX_BYTES = 512 * 1024;
|
|
32300
|
+
var CONSOLE_SNAPSHOT_INTERVAL_MS = 3e4;
|
|
32301
|
+
var CONSOLE_WATCH_LEASE_MS = 45e3;
|
|
32302
|
+
function toDtoEntry(e) {
|
|
32303
|
+
return { id: e.id, source: e.source, kind: e.kind, text: e.text, level: e.level, resultKind: e.resultKind };
|
|
32304
|
+
}
|
|
31400
32305
|
var WsClient = class {
|
|
31401
32306
|
constructor(context) {
|
|
31402
32307
|
this.context = context;
|
|
@@ -31408,6 +32313,11 @@ var WsClient = class {
|
|
|
31408
32313
|
);
|
|
31409
32314
|
context.bus.onLocalSubmit((input) => void this.runLocalCommand(input));
|
|
31410
32315
|
context.bus.onReconnect(() => this.reconnectNow());
|
|
32316
|
+
context.bus.onTranscript((entry) => this.emitConsole({ kind: "add", entry: toDtoEntry(entry) }));
|
|
32317
|
+
context.bus.onTranscriptUpdate(
|
|
32318
|
+
(entry) => this.emitConsole({ kind: "update", entry: toDtoEntry(entry) })
|
|
32319
|
+
);
|
|
32320
|
+
context.bus.onClear(() => this.emitConsoleClear());
|
|
31411
32321
|
}
|
|
31412
32322
|
context;
|
|
31413
32323
|
socket = null;
|
|
@@ -31431,6 +32341,13 @@ var WsClient = class {
|
|
|
31431
32341
|
wakeReconnect = null;
|
|
31432
32342
|
/** When set, the next backoff sleep returns immediately (forced reconnect). */
|
|
31433
32343
|
forceReconnect = false;
|
|
32344
|
+
/**
|
|
32345
|
+
* The single "reconnecting…" transcript line for the current outage (approach B): created on
|
|
32346
|
+
* the first failed attempt and updated in place each retry, then finalized to the "connected"
|
|
32347
|
+
* line on the next hello_ack — so one outage adds one line, not an error + a wait line per
|
|
32348
|
+
* attempt. Null while connected / between outages.
|
|
32349
|
+
*/
|
|
32350
|
+
reconnectEntryId = null;
|
|
31434
32351
|
/** Org-configured reconnect cap (seconds) from the last ws_token — overrides env. */
|
|
31435
32352
|
orgReconnectMaxSec = null;
|
|
31436
32353
|
/** Env override for the reconnect cap (seconds); resolved once at construction. */
|
|
@@ -31454,6 +32371,18 @@ var WsClient = class {
|
|
|
31454
32371
|
*/
|
|
31455
32372
|
handledCommands = /* @__PURE__ */ new Set();
|
|
31456
32373
|
offlineBufferBytes = 0;
|
|
32374
|
+
/**
|
|
32375
|
+
* Console-sync state (ADR-0150). `consoleWatching` is set by `console.watch` from the server
|
|
32376
|
+
* (a web Console viewer is attached); only then does the cli emit `console.sync`. `consoleRev`
|
|
32377
|
+
* is the monotonic revision the web uses to detect a gap; `consoleSnapshotTimer` periodically
|
|
32378
|
+
* re-sends a full snapshot to self-heal. Watching resets to false on each reconnect (the server
|
|
32379
|
+
* re-asserts it), so a fresh session re-snapshots.
|
|
32380
|
+
*/
|
|
32381
|
+
consoleWatching = false;
|
|
32382
|
+
consoleRev = 0;
|
|
32383
|
+
consoleSnapshotTimer = null;
|
|
32384
|
+
/** Lease expiry: stop syncing if the server does not renew `console.watch` in time. */
|
|
32385
|
+
consoleWatchExpiry = null;
|
|
31457
32386
|
/** cli → server requests awaiting a reply (quota.check — ADR-0020). */
|
|
31458
32387
|
pending = /* @__PURE__ */ new Map();
|
|
31459
32388
|
/** Daily scheduled cli auto-update (ADR-0074); policy fed from each ws_token. */
|
|
@@ -31487,14 +32416,14 @@ var WsClient = class {
|
|
|
31487
32416
|
*/
|
|
31488
32417
|
awaitConnected(timeoutMs) {
|
|
31489
32418
|
if (this.sessionKey && this.bus.status === "connected") return Promise.resolve(true);
|
|
31490
|
-
return new Promise((
|
|
32419
|
+
return new Promise((resolve8) => {
|
|
31491
32420
|
const waiter = () => {
|
|
31492
32421
|
clearTimeout(timer);
|
|
31493
|
-
|
|
32422
|
+
resolve8(true);
|
|
31494
32423
|
};
|
|
31495
32424
|
const timer = setTimeout(() => {
|
|
31496
32425
|
this.connectWaiters = this.connectWaiters.filter((w) => w !== waiter);
|
|
31497
|
-
|
|
32426
|
+
resolve8(false);
|
|
31498
32427
|
}, timeoutMs);
|
|
31499
32428
|
this.connectWaiters.push(waiter);
|
|
31500
32429
|
});
|
|
@@ -31521,16 +32450,16 @@ var WsClient = class {
|
|
|
31521
32450
|
this.forceReconnect = false;
|
|
31522
32451
|
return Promise.resolve();
|
|
31523
32452
|
}
|
|
31524
|
-
return new Promise((
|
|
32453
|
+
return new Promise((resolve8) => {
|
|
31525
32454
|
const timer = setTimeout(() => {
|
|
31526
32455
|
this.wakeReconnect = null;
|
|
31527
|
-
|
|
32456
|
+
resolve8();
|
|
31528
32457
|
}, ms);
|
|
31529
32458
|
this.wakeReconnect = () => {
|
|
31530
32459
|
clearTimeout(timer);
|
|
31531
32460
|
this.wakeReconnect = null;
|
|
31532
32461
|
this.forceReconnect = false;
|
|
31533
|
-
|
|
32462
|
+
resolve8();
|
|
31534
32463
|
};
|
|
31535
32464
|
});
|
|
31536
32465
|
}
|
|
@@ -31540,6 +32469,7 @@ var WsClient = class {
|
|
|
31540
32469
|
async run() {
|
|
31541
32470
|
let hashcodeInvalidStreak = 0;
|
|
31542
32471
|
for (let attempt = 0; !this.stopped; attempt++) {
|
|
32472
|
+
let reconnectDetail = "Disconnected";
|
|
31543
32473
|
try {
|
|
31544
32474
|
await this.connectOnce();
|
|
31545
32475
|
if (this.sessionEstablished) {
|
|
@@ -31557,17 +32487,14 @@ var WsClient = class {
|
|
|
31557
32487
|
this.logout(err.errorCode);
|
|
31558
32488
|
return;
|
|
31559
32489
|
}
|
|
31560
|
-
|
|
31561
|
-
`Token request rejected (HASHCODE_INVALID) \u2014 retrying (${hashcodeInvalidStreak}/${MAX_HASHCODE_INVALID_RETRIES}).`,
|
|
31562
|
-
"warn"
|
|
31563
|
-
);
|
|
32490
|
+
reconnectDetail = `Token rejected (HASHCODE_INVALID ${hashcodeInvalidStreak}/${MAX_HASHCODE_INVALID_RETRIES})`;
|
|
31564
32491
|
} else if (err.errorCode === "USER_PAUSED") {
|
|
31565
|
-
|
|
32492
|
+
reconnectDetail = "This account is paused \u2014 retrying until it is resumed";
|
|
31566
32493
|
} else {
|
|
31567
|
-
|
|
32494
|
+
reconnectDetail = `Connection error: ${err}`;
|
|
31568
32495
|
}
|
|
31569
32496
|
} else {
|
|
31570
|
-
|
|
32497
|
+
reconnectDetail = `Connection error: ${err}`;
|
|
31571
32498
|
}
|
|
31572
32499
|
}
|
|
31573
32500
|
if (this.stopped) return;
|
|
@@ -31578,7 +32505,7 @@ var WsClient = class {
|
|
|
31578
32505
|
);
|
|
31579
32506
|
logger.info("ws.reconnect", { attempt, waitMs: wait });
|
|
31580
32507
|
this.bus.setStatus("reconnecting");
|
|
31581
|
-
this.
|
|
32508
|
+
this.showReconnectStatus(reconnectDetail, Math.round(wait / 1e3), attempt + 1);
|
|
31582
32509
|
await this.interruptibleSleep(wait);
|
|
31583
32510
|
}
|
|
31584
32511
|
}
|
|
@@ -31586,7 +32513,7 @@ var WsClient = class {
|
|
|
31586
32513
|
* One WS session: token → connect → handshake → handle messages until close.
|
|
31587
32514
|
*/
|
|
31588
32515
|
connectOnce() {
|
|
31589
|
-
return new Promise((
|
|
32516
|
+
return new Promise((resolve8, reject) => {
|
|
31590
32517
|
void (async () => {
|
|
31591
32518
|
this.sessionEstablished = false;
|
|
31592
32519
|
this.bus.setStatus("connecting");
|
|
@@ -31655,7 +32582,7 @@ var WsClient = class {
|
|
|
31655
32582
|
if (code === CLOSE_DRAINING) {
|
|
31656
32583
|
this.bus.log("Server draining \u2014 will reconnect (backoff + jitter).");
|
|
31657
32584
|
}
|
|
31658
|
-
|
|
32585
|
+
resolve8();
|
|
31659
32586
|
});
|
|
31660
32587
|
socket.on("error", (err) => {
|
|
31661
32588
|
this.clearHeartbeat();
|
|
@@ -31682,8 +32609,9 @@ var WsClient = class {
|
|
|
31682
32609
|
this.physicRoot = null;
|
|
31683
32610
|
}
|
|
31684
32611
|
logger.info("ws.connected", { version: CLI_VERSION });
|
|
32612
|
+
this.stopConsoleSync();
|
|
31685
32613
|
this.bus.setStatus("connected");
|
|
31686
|
-
this.
|
|
32614
|
+
this.finishReconnect();
|
|
31687
32615
|
this.sendMachineStatus();
|
|
31688
32616
|
this.flushOfflineBuffer();
|
|
31689
32617
|
this.startHeartbeat();
|
|
@@ -31746,8 +32674,12 @@ var WsClient = class {
|
|
|
31746
32674
|
switch (message.channel) {
|
|
31747
32675
|
case WsChannels.PHYSIC_SYNC: {
|
|
31748
32676
|
const sync = payload;
|
|
31749
|
-
if (sync.oldName)
|
|
31750
|
-
|
|
32677
|
+
if (sync.oldName) {
|
|
32678
|
+
const oldRoot = this.physicFolderPath(sync.oldName);
|
|
32679
|
+
if (oldRoot) void uninstallCron(oldRoot).catch(() => void 0);
|
|
32680
|
+
}
|
|
32681
|
+
if (sync.oldName) rmSync4(join19(this.context.profileDir, sync.oldName), { recursive: true, force: true });
|
|
32682
|
+
mkdirSync4(join19(this.context.profileDir, sync.newName), { recursive: true });
|
|
31751
32683
|
this.physicRoot = this.physicFolderPath(sync.newName);
|
|
31752
32684
|
this.bus.setProject(sync.newName);
|
|
31753
32685
|
this.bus.log(`Physic project folder synced \u2192 ${sync.newName}`);
|
|
@@ -31756,7 +32688,9 @@ var WsClient = class {
|
|
|
31756
32688
|
case WsChannels.PHYSIC_DELETE: {
|
|
31757
32689
|
const del = payload;
|
|
31758
32690
|
if (del.name) {
|
|
31759
|
-
|
|
32691
|
+
const delRoot = this.physicFolderPath(del.name);
|
|
32692
|
+
if (delRoot) void uninstallCron(delRoot).catch(() => void 0);
|
|
32693
|
+
rmSync4(join19(this.context.profileDir, del.name), { recursive: true, force: true });
|
|
31760
32694
|
this.bus.log(`Physic project folder deleted (project removed) \u2192 ${del.name}`);
|
|
31761
32695
|
}
|
|
31762
32696
|
this.bus.setProject(null);
|
|
@@ -31821,6 +32755,135 @@ var WsClient = class {
|
|
|
31821
32755
|
(reply) => this.send(WsChannels.FS_READ, reply, message.id)
|
|
31822
32756
|
);
|
|
31823
32757
|
break;
|
|
32758
|
+
case WsChannels.FS_WRITE: {
|
|
32759
|
+
const req = payload;
|
|
32760
|
+
void writeWorkerFile(this.physicRoot, req.path, req.content).then(
|
|
32761
|
+
(reply) => this.send(WsChannels.FS_WRITE, reply, message.id)
|
|
32762
|
+
);
|
|
32763
|
+
break;
|
|
32764
|
+
}
|
|
32765
|
+
case WsChannels.AUTONOMOUS_READ:
|
|
32766
|
+
if (this.physicRoot) {
|
|
32767
|
+
void readAutonomous(this.physicRoot).then(
|
|
32768
|
+
(reply) => this.send(WsChannels.AUTONOMOUS_READ, reply, message.id)
|
|
32769
|
+
);
|
|
32770
|
+
}
|
|
32771
|
+
break;
|
|
32772
|
+
case WsChannels.AUTONOMOUS_LOGS: {
|
|
32773
|
+
const req = payload;
|
|
32774
|
+
if (this.physicRoot) {
|
|
32775
|
+
void readAutonomousLogs(this.physicRoot, req.date).then(
|
|
32776
|
+
(reply) => this.send(WsChannels.AUTONOMOUS_LOGS, reply, message.id)
|
|
32777
|
+
);
|
|
32778
|
+
}
|
|
32779
|
+
break;
|
|
32780
|
+
}
|
|
32781
|
+
case WsChannels.AUTONOMOUS_WRITE: {
|
|
32782
|
+
const req = payload;
|
|
32783
|
+
if (this.physicRoot) {
|
|
32784
|
+
void writeAutonomous(this.physicRoot, req, req.by ?? "unknown").then(
|
|
32785
|
+
(reply) => this.send(WsChannels.AUTONOMOUS_WRITE, reply, message.id)
|
|
32786
|
+
);
|
|
32787
|
+
}
|
|
32788
|
+
break;
|
|
32789
|
+
}
|
|
32790
|
+
case WsChannels.AGENTS_LIST:
|
|
32791
|
+
if (this.physicRoot) {
|
|
32792
|
+
void listAgents(this.physicRoot).then(
|
|
32793
|
+
(reply) => this.send(WsChannels.AGENTS_LIST, reply, message.id)
|
|
32794
|
+
);
|
|
32795
|
+
}
|
|
32796
|
+
break;
|
|
32797
|
+
case WsChannels.AGENTS_READ: {
|
|
32798
|
+
const req = payload;
|
|
32799
|
+
if (this.physicRoot) {
|
|
32800
|
+
void readAgent(this.physicRoot, req.kind, req.name).then(
|
|
32801
|
+
(reply) => this.send(WsChannels.AGENTS_READ, reply, message.id)
|
|
32802
|
+
);
|
|
32803
|
+
}
|
|
32804
|
+
break;
|
|
32805
|
+
}
|
|
32806
|
+
case WsChannels.AGENTS_WRITE: {
|
|
32807
|
+
const req = payload;
|
|
32808
|
+
if (this.physicRoot) {
|
|
32809
|
+
void writeAgent(this.physicRoot, req).then(
|
|
32810
|
+
(reply) => this.send(WsChannels.AGENTS_WRITE, reply, message.id)
|
|
32811
|
+
);
|
|
32812
|
+
}
|
|
32813
|
+
break;
|
|
32814
|
+
}
|
|
32815
|
+
case WsChannels.SECRETS_READ:
|
|
32816
|
+
if (this.physicRoot) {
|
|
32817
|
+
void readSecrets(this.physicRoot).then(
|
|
32818
|
+
(reply) => this.send(WsChannels.SECRETS_READ, reply, message.id)
|
|
32819
|
+
);
|
|
32820
|
+
}
|
|
32821
|
+
break;
|
|
32822
|
+
case WsChannels.SECRETS_WRITE: {
|
|
32823
|
+
const req = payload;
|
|
32824
|
+
if (this.physicRoot) {
|
|
32825
|
+
void writeSecrets(this.physicRoot, req).then(
|
|
32826
|
+
(reply) => this.send(WsChannels.SECRETS_WRITE, reply, message.id)
|
|
32827
|
+
);
|
|
32828
|
+
}
|
|
32829
|
+
break;
|
|
32830
|
+
}
|
|
32831
|
+
case WsChannels.GRAPH_BUILD: {
|
|
32832
|
+
const req = payload;
|
|
32833
|
+
if (this.physicRoot) {
|
|
32834
|
+
void buildGraph(this.physicRoot, req.mode).then(
|
|
32835
|
+
(reply) => this.send(WsChannels.GRAPH_BUILD, reply, message.id)
|
|
32836
|
+
);
|
|
32837
|
+
}
|
|
32838
|
+
break;
|
|
32839
|
+
}
|
|
32840
|
+
case WsChannels.RAG_STATUS:
|
|
32841
|
+
if (this.physicRoot) {
|
|
32842
|
+
void ragStatus(this.physicRoot).then(
|
|
32843
|
+
(reply) => this.send(WsChannels.RAG_STATUS, reply, message.id)
|
|
32844
|
+
);
|
|
32845
|
+
}
|
|
32846
|
+
break;
|
|
32847
|
+
case WsChannels.RAG_INSTALL: {
|
|
32848
|
+
const req = payload;
|
|
32849
|
+
if (this.physicRoot) {
|
|
32850
|
+
void ragInstall(this.physicRoot, req.model).then(
|
|
32851
|
+
(reply) => this.send(WsChannels.RAG_INSTALL, reply, message.id)
|
|
32852
|
+
);
|
|
32853
|
+
}
|
|
32854
|
+
break;
|
|
32855
|
+
}
|
|
32856
|
+
case WsChannels.RAG_REINDEX:
|
|
32857
|
+
if (this.physicRoot) {
|
|
32858
|
+
void ragReindex(this.physicRoot).then(
|
|
32859
|
+
(reply) => this.send(WsChannels.RAG_REINDEX, reply, message.id)
|
|
32860
|
+
);
|
|
32861
|
+
}
|
|
32862
|
+
break;
|
|
32863
|
+
case WsChannels.RAG_QUERY: {
|
|
32864
|
+
const req = payload;
|
|
32865
|
+
if (this.physicRoot) {
|
|
32866
|
+
void ragQuery(this.physicRoot, req.query, req.k).then(
|
|
32867
|
+
(reply) => this.send(WsChannels.RAG_QUERY, reply, message.id)
|
|
32868
|
+
);
|
|
32869
|
+
}
|
|
32870
|
+
break;
|
|
32871
|
+
}
|
|
32872
|
+
case WsChannels.CONFIG_READ:
|
|
32873
|
+
this.send(
|
|
32874
|
+
WsChannels.CONFIG_READ,
|
|
32875
|
+
{ config: readConfigText(this.context.profileDir) },
|
|
32876
|
+
message.id
|
|
32877
|
+
);
|
|
32878
|
+
break;
|
|
32879
|
+
case WsChannels.CONFIG_WRITE: {
|
|
32880
|
+
const res = applyConfigText(
|
|
32881
|
+
this.context.profileDir,
|
|
32882
|
+
payload.config
|
|
32883
|
+
);
|
|
32884
|
+
this.send(WsChannels.CONFIG_WRITE, res, message.id);
|
|
32885
|
+
break;
|
|
32886
|
+
}
|
|
31824
32887
|
case WsChannels.GIT_DIFF:
|
|
31825
32888
|
void gitDiff(payload.path).then(
|
|
31826
32889
|
(reply) => this.send(WsChannels.GIT_DIFF, reply, message.id)
|
|
@@ -31859,7 +32922,7 @@ var WsClient = class {
|
|
|
31859
32922
|
}
|
|
31860
32923
|
case WsChannels.LOG_READ: {
|
|
31861
32924
|
const limit = Math.min(Math.max(payload.limit ?? 200, 1), 2e3);
|
|
31862
|
-
const lines = readRecentLogLines(
|
|
32925
|
+
const lines = readRecentLogLines(join19(this.context.profileDir, "logs"), limit);
|
|
31863
32926
|
this.send(WsChannels.LOG_READ, { lines }, message.id);
|
|
31864
32927
|
break;
|
|
31865
32928
|
}
|
|
@@ -31889,6 +32952,9 @@ var WsClient = class {
|
|
|
31889
32952
|
(reply) => this.send(WsChannels.REVIEW_EVALUATE, reply, message.id)
|
|
31890
32953
|
);
|
|
31891
32954
|
break;
|
|
32955
|
+
case WsChannels.CONSOLE_WATCH:
|
|
32956
|
+
this.setConsoleWatching(payload.on);
|
|
32957
|
+
break;
|
|
31892
32958
|
default:
|
|
31893
32959
|
this.bus.log(`Unsupported channel: ${message.channel}`, "warn");
|
|
31894
32960
|
}
|
|
@@ -31933,7 +32999,7 @@ var WsClient = class {
|
|
|
31933
32999
|
if (perPromptLimit > 0) {
|
|
31934
33000
|
const estimated = estimateTokens(prompt);
|
|
31935
33001
|
if (estimated > perPromptLimit) {
|
|
31936
|
-
this.rejectPromptOverLimit(commandId, origin, estimated, perPromptLimit);
|
|
33002
|
+
this.rejectPromptOverLimit(commandId, origin, prompt, estimated, perPromptLimit);
|
|
31937
33003
|
return;
|
|
31938
33004
|
}
|
|
31939
33005
|
}
|
|
@@ -31948,7 +33014,7 @@ var WsClient = class {
|
|
|
31948
33014
|
verdict = { commandId, ok: false, reasons: ["unavailable"] };
|
|
31949
33015
|
}
|
|
31950
33016
|
if (!verdict.ok) {
|
|
31951
|
-
this.rejectByReview(commandId, origin, verdict.reasons);
|
|
33017
|
+
this.rejectByReview(commandId, origin, prompt, verdict.reasons);
|
|
31952
33018
|
return;
|
|
31953
33019
|
}
|
|
31954
33020
|
}
|
|
@@ -31978,6 +33044,8 @@ var WsClient = class {
|
|
|
31978
33044
|
commandId,
|
|
31979
33045
|
cmd: plan.cmd,
|
|
31980
33046
|
args: markerArgs,
|
|
33047
|
+
// The raw prompt so the web console echoes it like this TUI (ADR-0108), not "claude".
|
|
33048
|
+
prompt,
|
|
31981
33049
|
origin: "local"
|
|
31982
33050
|
});
|
|
31983
33051
|
}
|
|
@@ -32017,17 +33085,18 @@ var WsClient = class {
|
|
|
32017
33085
|
this.bus.push({ source: origin, kind: "out", text: resultBuf });
|
|
32018
33086
|
}
|
|
32019
33087
|
},
|
|
32020
|
-
onAttemptStart: (label, index, total) =>
|
|
32021
|
-
|
|
32022
|
-
kind: "log",
|
|
32023
|
-
|
|
32024
|
-
|
|
32025
|
-
|
|
32026
|
-
|
|
32027
|
-
|
|
32028
|
-
|
|
32029
|
-
|
|
32030
|
-
|
|
33088
|
+
onAttemptStart: (label, index, total) => {
|
|
33089
|
+
const text = `\u2192 trying ${plan.cmd} profile "${label}" (${index + 1}/${total})\u2026`;
|
|
33090
|
+
this.bus.push({ source: origin, kind: "log", text });
|
|
33091
|
+
this.send(WsChannels.COMMAND_OUTPUT, { commandId, seq: seq++, chunk: `${text}
|
|
33092
|
+
`, log: true });
|
|
33093
|
+
},
|
|
33094
|
+
onAttemptFail: (label, reason) => {
|
|
33095
|
+
const text = reason === "limit" ? `profile "${label}" hit its session limit \u2014 trying next` : `profile "${label}" failed to authenticate \u2014 trying next`;
|
|
33096
|
+
this.bus.push({ source: origin, kind: "log", text, level: "warn" });
|
|
33097
|
+
this.send(WsChannels.COMMAND_OUTPUT, { commandId, seq: seq++, chunk: `${text}
|
|
33098
|
+
`, log: true });
|
|
33099
|
+
}
|
|
32031
33100
|
});
|
|
32032
33101
|
this.bus.endBusy(plan.cmd);
|
|
32033
33102
|
const profileLabel = result.workedDir ? profileDisplayLabel(result.workedDir) : null;
|
|
@@ -32050,6 +33119,15 @@ var WsClient = class {
|
|
|
32050
33119
|
status: result.exitCode === 0 ? "done" : "failed",
|
|
32051
33120
|
exitCode: result.exitCode,
|
|
32052
33121
|
tokens: result.usage.tokens,
|
|
33122
|
+
// The ai_tokens split for this run (ADR-0145); attached only when we metered real tokens.
|
|
33123
|
+
...result.usage.tokens > 0 && {
|
|
33124
|
+
tokensBreakdown: {
|
|
33125
|
+
input: result.usage.input,
|
|
33126
|
+
output: result.usage.output,
|
|
33127
|
+
cacheRead: result.usage.cacheRead,
|
|
33128
|
+
cacheCreation: result.usage.cacheCreation
|
|
33129
|
+
}
|
|
33130
|
+
},
|
|
32053
33131
|
startedAt,
|
|
32054
33132
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
32055
33133
|
});
|
|
@@ -32058,7 +33136,16 @@ var WsClient = class {
|
|
|
32058
33136
|
{ metric: UsageMetric.COMMANDS, amount: 1, occurredAt }
|
|
32059
33137
|
];
|
|
32060
33138
|
if (result.exitCode === 0 && result.usage.tokens > 0) {
|
|
32061
|
-
events.push({
|
|
33139
|
+
events.push({
|
|
33140
|
+
metric: UsageMetric.AI_TOKENS,
|
|
33141
|
+
amount: result.usage.tokens,
|
|
33142
|
+
occurredAt,
|
|
33143
|
+
// The ai_tokens split so the server can persist + display it (ADR-0145).
|
|
33144
|
+
inputTokens: result.usage.input,
|
|
33145
|
+
outputTokens: result.usage.output,
|
|
33146
|
+
cacheReadTokens: result.usage.cacheRead,
|
|
33147
|
+
cacheCreationTokens: result.usage.cacheCreation
|
|
33148
|
+
});
|
|
32062
33149
|
this.bus.addTokens?.(result.usage.tokens);
|
|
32063
33150
|
}
|
|
32064
33151
|
this.reportUsage(events, profileLabel);
|
|
@@ -32093,7 +33180,7 @@ var WsClient = class {
|
|
|
32093
33180
|
* without spawning: surface it in the transcript + close the command on the server with a
|
|
32094
33181
|
* PROMPT_TOKEN_LIMIT_EXCEEDED note (exit 1). No usage is metered (nothing ran).
|
|
32095
33182
|
*/
|
|
32096
|
-
rejectPromptOverLimit(commandId, origin, estimated, limit) {
|
|
33183
|
+
rejectPromptOverLimit(commandId, origin, prompt, estimated, limit) {
|
|
32097
33184
|
const message = `[PROMPT_TOKEN_LIMIT_EXCEEDED] Prompt (~${estimated} tokens) exceeds the project per-prompt limit of ${limit} tokens.`;
|
|
32098
33185
|
logger.warn("command.ai.prompt-limit", { commandId, origin, estimated, limit });
|
|
32099
33186
|
this.bus.push({ source: origin, kind: "log", text: message, level: "error" });
|
|
@@ -32102,6 +33189,7 @@ var WsClient = class {
|
|
|
32102
33189
|
commandId,
|
|
32103
33190
|
cmd: "claude",
|
|
32104
33191
|
args: [],
|
|
33192
|
+
prompt,
|
|
32105
33193
|
origin: "local"
|
|
32106
33194
|
});
|
|
32107
33195
|
}
|
|
@@ -32119,7 +33207,7 @@ var WsClient = class {
|
|
|
32119
33207
|
* verdict + close the command on the server. `reasons` contains only violation categories
|
|
32120
33208
|
* (never secret values); a "unavailable" reason maps to no-outbound-available.
|
|
32121
33209
|
*/
|
|
32122
|
-
rejectByReview(commandId, origin, reasons) {
|
|
33210
|
+
rejectByReview(commandId, origin, prompt, reasons) {
|
|
32123
33211
|
const unavailable = reasons.includes("unavailable");
|
|
32124
33212
|
const code = unavailable ? "OUTBOUND_REVIEW_UNAVAILABLE" : "OUTBOUND_REVIEW_REJECTED";
|
|
32125
33213
|
const detail = reasons.filter((r) => r !== "unavailable").join(", ");
|
|
@@ -32131,6 +33219,7 @@ var WsClient = class {
|
|
|
32131
33219
|
commandId,
|
|
32132
33220
|
cmd: "claude",
|
|
32133
33221
|
args: [],
|
|
33222
|
+
prompt,
|
|
32134
33223
|
origin: "local"
|
|
32135
33224
|
});
|
|
32136
33225
|
}
|
|
@@ -32164,12 +33253,105 @@ var WsClient = class {
|
|
|
32164
33253
|
};
|
|
32165
33254
|
this.socket.send(JSON.stringify(envelope));
|
|
32166
33255
|
}
|
|
33256
|
+
/**
|
|
33257
|
+
* Console sync (ADR-0150): emit one incremental `console.sync` event (add/update/clear) — only
|
|
33258
|
+
* while a web Console viewer is attached (`consoleWatching`); each carries the next `rev` so the
|
|
33259
|
+
* web detects a gap. Dropped when disconnected — the next snapshot on (re)attach re-syncs.
|
|
33260
|
+
*/
|
|
33261
|
+
emitConsole(ev) {
|
|
33262
|
+
if (!this.consoleWatching) return;
|
|
33263
|
+
this.send(WsChannels.CONSOLE_SYNC, { ...ev, rev: ++this.consoleRev });
|
|
33264
|
+
}
|
|
33265
|
+
/**
|
|
33266
|
+
* A `/clear` (or idle auto-clear) wiped the transcript (ADR-0150). Emit it as an absolute empty
|
|
33267
|
+
* **snapshot** rather than an incremental `clear` event: a snapshot is applied unconditionally on
|
|
33268
|
+
* the web (no `rev`-gap check), so the clear can't be silently dropped when the web's revision
|
|
33269
|
+
* drifted during a reconnect flap. No-op while unwatched — the next `console.watch` re-arm
|
|
33270
|
+
* snapshots the (now empty) buffer anyway. The history is already emptied before this fires, so
|
|
33271
|
+
* the snapshot carries no entries.
|
|
33272
|
+
*/
|
|
33273
|
+
emitConsoleClear() {
|
|
33274
|
+
if (!this.consoleWatching) return;
|
|
33275
|
+
this.sendConsoleSnapshot();
|
|
33276
|
+
}
|
|
33277
|
+
/**
|
|
33278
|
+
* Show the reconnect-backoff status as a SINGLE transcript line for the current outage
|
|
33279
|
+
* (approach B): create it on the first failed attempt, then update it in place on every retry —
|
|
33280
|
+
* so an outage adds one line instead of an error + a "reconnecting in Ns" line per attempt
|
|
33281
|
+
* (which flooded the TUI and, on the next snapshot, the web Console).
|
|
33282
|
+
*/
|
|
33283
|
+
showReconnectStatus(detail, waitSec, attempt) {
|
|
33284
|
+
const text = `\u27F3 ${detail} \u2014 reconnecting in ${waitSec}s (attempt ${attempt}, /reconnect to retry now)`;
|
|
33285
|
+
if (this.reconnectEntryId) {
|
|
33286
|
+
this.bus.updateEntry(this.reconnectEntryId, text, "warn");
|
|
33287
|
+
} else {
|
|
33288
|
+
this.reconnectEntryId = this.bus.push({ source: "system", kind: "log", text, level: "warn" });
|
|
33289
|
+
}
|
|
33290
|
+
}
|
|
33291
|
+
/**
|
|
33292
|
+
* A session became ready (hello_ack): finalize any in-flight reconnect line in place to the
|
|
33293
|
+
* "connected" line (so the outage's single line resolves cleanly), or — on a first connect with
|
|
33294
|
+
* no prior outage — push a fresh connected line.
|
|
33295
|
+
*/
|
|
33296
|
+
finishReconnect() {
|
|
33297
|
+
const msg = "\u2714 Connected to the server (session encryption ready).";
|
|
33298
|
+
if (this.reconnectEntryId) {
|
|
33299
|
+
this.bus.updateEntry(this.reconnectEntryId, msg, "info");
|
|
33300
|
+
this.reconnectEntryId = null;
|
|
33301
|
+
} else {
|
|
33302
|
+
this.bus.log(msg);
|
|
33303
|
+
}
|
|
33304
|
+
}
|
|
33305
|
+
/** Send a full transcript snapshot (on attach + the periodic self-heal — ADR-0150). */
|
|
33306
|
+
sendConsoleSnapshot() {
|
|
33307
|
+
const entries2 = this.bus.snapshot().map(toDtoEntry);
|
|
33308
|
+
this.send(WsChannels.CONSOLE_SYNC, {
|
|
33309
|
+
kind: "snapshot",
|
|
33310
|
+
rev: ++this.consoleRev,
|
|
33311
|
+
entries: entries2
|
|
33312
|
+
});
|
|
33313
|
+
}
|
|
33314
|
+
/**
|
|
33315
|
+
* Handle `console.watch` (ADR-0150) — a renewable lease. `on` renews it: start mirroring on the
|
|
33316
|
+
* first assertion (fresh snapshot + arm the periodic snapshot); later renewals just extend the
|
|
33317
|
+
* lease (no re-snapshot). No renewal within `CONSOLE_WATCH_LEASE_MS` ⇒ the lease expires and
|
|
33318
|
+
* syncing stops. An explicit `on:false` stops immediately.
|
|
33319
|
+
*/
|
|
33320
|
+
setConsoleWatching(on) {
|
|
33321
|
+
if (!on) {
|
|
33322
|
+
this.stopConsoleSync();
|
|
33323
|
+
return;
|
|
33324
|
+
}
|
|
33325
|
+
if (this.consoleWatchExpiry) clearTimeout(this.consoleWatchExpiry);
|
|
33326
|
+
this.consoleWatchExpiry = setTimeout(() => this.stopConsoleSync(), CONSOLE_WATCH_LEASE_MS);
|
|
33327
|
+
this.consoleWatchExpiry.unref?.();
|
|
33328
|
+
if (this.consoleWatching) return;
|
|
33329
|
+
this.consoleWatching = true;
|
|
33330
|
+
this.sendConsoleSnapshot();
|
|
33331
|
+
this.consoleSnapshotTimer = setInterval(
|
|
33332
|
+
() => this.sendConsoleSnapshot(),
|
|
33333
|
+
CONSOLE_SNAPSHOT_INTERVAL_MS
|
|
33334
|
+
);
|
|
33335
|
+
this.consoleSnapshotTimer.unref?.();
|
|
33336
|
+
}
|
|
33337
|
+
/** Stop mirroring the transcript (lease expired / viewer left / reconnect) — clears both timers. */
|
|
33338
|
+
stopConsoleSync() {
|
|
33339
|
+
this.consoleWatching = false;
|
|
33340
|
+
if (this.consoleSnapshotTimer) {
|
|
33341
|
+
clearInterval(this.consoleSnapshotTimer);
|
|
33342
|
+
this.consoleSnapshotTimer = null;
|
|
33343
|
+
}
|
|
33344
|
+
if (this.consoleWatchExpiry) {
|
|
33345
|
+
clearTimeout(this.consoleWatchExpiry);
|
|
33346
|
+
this.consoleWatchExpiry = null;
|
|
33347
|
+
}
|
|
33348
|
+
}
|
|
32167
33349
|
/**
|
|
32168
33350
|
* Send a cli → server request and await the reply by envelope id (15s timeout).
|
|
32169
33351
|
* Used for quota.check before spawning an AI cli (ADR-0020).
|
|
32170
33352
|
*/
|
|
32171
33353
|
request(channel, data) {
|
|
32172
|
-
return new Promise((
|
|
33354
|
+
return new Promise((resolve8, reject) => {
|
|
32173
33355
|
if (!this.socket || !this.sessionKey) {
|
|
32174
33356
|
reject(new Error("Not connected to the server."));
|
|
32175
33357
|
return;
|
|
@@ -32189,7 +33371,7 @@ var WsClient = class {
|
|
|
32189
33371
|
}, 15e3);
|
|
32190
33372
|
timer.unref?.();
|
|
32191
33373
|
this.pending.set(envelope.id, {
|
|
32192
|
-
resolve: (value) =>
|
|
33374
|
+
resolve: (value) => resolve8(value),
|
|
32193
33375
|
timer
|
|
32194
33376
|
});
|
|
32195
33377
|
this.socket.send(JSON.stringify(envelope));
|
|
@@ -32236,7 +33418,7 @@ var WsClient = class {
|
|
|
32236
33418
|
/** Create the physic folder at an absolute path if missing (best-effort). */
|
|
32237
33419
|
ensurePhysicFolderPath(folder) {
|
|
32238
33420
|
try {
|
|
32239
|
-
if (!
|
|
33421
|
+
if (!existsSync9(folder)) {
|
|
32240
33422
|
mkdirSync4(folder, { recursive: true });
|
|
32241
33423
|
this.bus.log(`Created physic project folder: ${folder}`);
|
|
32242
33424
|
}
|
|
@@ -32251,7 +33433,7 @@ var WsClient = class {
|
|
|
32251
33433
|
*/
|
|
32252
33434
|
physicFolderPath(projectName) {
|
|
32253
33435
|
const safe = projectName.replace(/[/\\]/g, "_").replace(/\.\./g, "_").trim();
|
|
32254
|
-
return safe ?
|
|
33436
|
+
return safe ? join19(this.context.profileDir, safe) : null;
|
|
32255
33437
|
}
|
|
32256
33438
|
/**
|
|
32257
33439
|
* Report machine.status: version + the state of the physic project being served
|
|
@@ -32265,8 +33447,9 @@ var WsClient = class {
|
|
|
32265
33447
|
projects: [],
|
|
32266
33448
|
version: CLI_VERSION,
|
|
32267
33449
|
physicPath,
|
|
32268
|
-
physicPathExists: physicPath ?
|
|
32269
|
-
|
|
33450
|
+
physicPathExists: physicPath ? existsSync9(physicPath) : void 0,
|
|
33451
|
+
// Real coarse state (ADR-0152): !paused and a recent tick (was hard-coded false).
|
|
33452
|
+
autonomousRunning: physicPath ? isAutonomousRunning(physicPath) : false
|
|
32270
33453
|
});
|
|
32271
33454
|
}
|
|
32272
33455
|
/** Heartbeat every 30s: ping + periodically re-report machine.status. */
|
|
@@ -32299,7 +33482,7 @@ var WsClient = class {
|
|
|
32299
33482
|
const working = getWorkingProfile(this.context.profileDir, aiCli2);
|
|
32300
33483
|
const configured = claudeHomeDirs(config);
|
|
32301
33484
|
const dirs = working ? [working, ...configured.filter((d) => d !== working)] : configured;
|
|
32302
|
-
const profile = working ?
|
|
33485
|
+
const profile = working ? basename4(working) : dirs[0] ? basename4(dirs[0]) : "default";
|
|
32303
33486
|
const api = await checkClaudeUsage(dirs, aiCli2);
|
|
32304
33487
|
const snapshot = api ?? {
|
|
32305
33488
|
plan: "?",
|
|
@@ -32336,7 +33519,7 @@ var WsClient = class {
|
|
|
32336
33519
|
*/
|
|
32337
33520
|
uploadLog() {
|
|
32338
33521
|
try {
|
|
32339
|
-
const upload = readLogUpload(
|
|
33522
|
+
const upload = readLogUpload(join19(this.context.profileDir, "logs"));
|
|
32340
33523
|
if (!upload) return;
|
|
32341
33524
|
const sig = `${upload.fileName}:${upload.totalBytes}`;
|
|
32342
33525
|
if (sig === this.lastLogSig) return;
|
|
@@ -32364,10 +33547,10 @@ var WsClient = class {
|
|
|
32364
33547
|
};
|
|
32365
33548
|
|
|
32366
33549
|
// src/core/instance-lock.ts
|
|
32367
|
-
import { readFileSync as
|
|
32368
|
-
import { join as
|
|
33550
|
+
import { readFileSync as readFileSync14, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
33551
|
+
import { join as join20 } from "path";
|
|
32369
33552
|
function lockPath(profileDir2) {
|
|
32370
|
-
return
|
|
33553
|
+
return join20(profileDir2, "start.lock");
|
|
32371
33554
|
}
|
|
32372
33555
|
function isAlive(pid) {
|
|
32373
33556
|
try {
|
|
@@ -32379,7 +33562,7 @@ function isAlive(pid) {
|
|
|
32379
33562
|
}
|
|
32380
33563
|
function lockHolder(profileDir2) {
|
|
32381
33564
|
try {
|
|
32382
|
-
const pid = Number.parseInt(
|
|
33565
|
+
const pid = Number.parseInt(readFileSync14(lockPath(profileDir2), "utf8").trim(), 10);
|
|
32383
33566
|
if (Number.isInteger(pid) && pid > 0 && isAlive(pid)) return pid;
|
|
32384
33567
|
} catch {
|
|
32385
33568
|
}
|
|
@@ -32392,7 +33575,7 @@ function acquireInstanceLock(profileDir2) {
|
|
|
32392
33575
|
}
|
|
32393
33576
|
function releaseInstanceLock(profileDir2) {
|
|
32394
33577
|
try {
|
|
32395
|
-
const pid = Number.parseInt(
|
|
33578
|
+
const pid = Number.parseInt(readFileSync14(lockPath(profileDir2), "utf8").trim(), 10);
|
|
32396
33579
|
if (pid === process.pid) rmSync5(lockPath(profileDir2), { force: true });
|
|
32397
33580
|
} catch {
|
|
32398
33581
|
}
|
|
@@ -32442,14 +33625,33 @@ var SessionBus = class {
|
|
|
32442
33625
|
return entry.id;
|
|
32443
33626
|
}
|
|
32444
33627
|
/**
|
|
32445
|
-
*
|
|
32446
|
-
*
|
|
32447
|
-
*
|
|
33628
|
+
* Wipe the transcript (`/clear` + idle auto-clear — ADR-0057): empty the replay history and
|
|
33629
|
+
* notify consumers so the TUI clears the screen and the console-sync emitter (ADR-0150) sends
|
|
33630
|
+
* a `clear` event. The current snapshot afterwards is empty (until new lines arrive).
|
|
32448
33631
|
*/
|
|
32449
|
-
|
|
33632
|
+
clear() {
|
|
33633
|
+
this.history.length = 0;
|
|
33634
|
+
this.emitter.emit("clear");
|
|
33635
|
+
}
|
|
33636
|
+
/** Subscribe to transcript clears (TUI screen wipe + console.sync `clear` — ADR-0150). */
|
|
33637
|
+
onClear(fn) {
|
|
33638
|
+
this.emitter.on("clear", fn);
|
|
33639
|
+
return () => this.emitter.off("clear", fn);
|
|
33640
|
+
}
|
|
33641
|
+
/** The current transcript entries (a copy) — the console-sync snapshot source (ADR-0150). */
|
|
33642
|
+
snapshot() {
|
|
33643
|
+
return [...this.history];
|
|
33644
|
+
}
|
|
33645
|
+
/**
|
|
33646
|
+
* Producer → consumers: replace the text (and optionally the level) of an already-pushed entry
|
|
33647
|
+
* (ADR-0108) — used to grow a streaming `result` block in place, or to update a single reconnect
|
|
33648
|
+
* status line without pushing a new line per attempt. No-op if the entry has aged out.
|
|
33649
|
+
*/
|
|
33650
|
+
updateEntry(id, text, level) {
|
|
32450
33651
|
const entry = this.history.find((e) => e.id === id);
|
|
32451
33652
|
if (!entry) return;
|
|
32452
33653
|
entry.text = text;
|
|
33654
|
+
if (level) entry.level = level;
|
|
32453
33655
|
this.emitter.emit("transcript-update", entry);
|
|
32454
33656
|
}
|
|
32455
33657
|
/** Subscribe to in-place entry updates (a `result` block growing — ADR-0108). */
|
|
@@ -35658,7 +36860,7 @@ var styles_default = styles2;
|
|
|
35658
36860
|
// ../../node_modules/.pnpm/ink@5.2.1_@types+react@18.3.31_react@18.3.1/node_modules/ink/build/reconciler.js
|
|
35659
36861
|
if (process3.env["DEV"] === "true") {
|
|
35660
36862
|
try {
|
|
35661
|
-
await import("./devtools-
|
|
36863
|
+
await import("./devtools-ZL4ACXHE.js");
|
|
35662
36864
|
} catch (error) {
|
|
35663
36865
|
if (error.code === "ERR_MODULE_NOT_FOUND") {
|
|
35664
36866
|
console.warn(`
|
|
@@ -37876,8 +39078,8 @@ var Ink = class {
|
|
|
37876
39078
|
}
|
|
37877
39079
|
}
|
|
37878
39080
|
async waitUntilExit() {
|
|
37879
|
-
this.exitPromise ||= new Promise((
|
|
37880
|
-
this.resolveExitPromise =
|
|
39081
|
+
this.exitPromise ||= new Promise((resolve8, reject) => {
|
|
39082
|
+
this.resolveExitPromise = resolve8;
|
|
37881
39083
|
this.rejectExitPromise = reject;
|
|
37882
39084
|
});
|
|
37883
39085
|
return this.exitPromise;
|
|
@@ -38244,17 +39446,17 @@ var import_react21 = __toESM(require_react(), 1);
|
|
|
38244
39446
|
var import_react26 = __toESM(require_react(), 1);
|
|
38245
39447
|
|
|
38246
39448
|
// src/core/input-history.ts
|
|
38247
|
-
import { existsSync as
|
|
38248
|
-
import { join as
|
|
39449
|
+
import { existsSync as existsSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync7 } from "fs";
|
|
39450
|
+
import { join as join21 } from "path";
|
|
38249
39451
|
var MAX_HISTORY = 200;
|
|
38250
39452
|
function historyPath(profileDir2) {
|
|
38251
|
-
return
|
|
39453
|
+
return join21(profileDir2, "input-history.json");
|
|
38252
39454
|
}
|
|
38253
39455
|
function loadInputHistory(profileDir2) {
|
|
38254
39456
|
const path = historyPath(profileDir2);
|
|
38255
|
-
if (!
|
|
39457
|
+
if (!existsSync11(path)) return [];
|
|
38256
39458
|
try {
|
|
38257
|
-
const data = JSON.parse(
|
|
39459
|
+
const data = JSON.parse(readFileSync16(path, "utf8"));
|
|
38258
39460
|
return Array.isArray(data) ? data.filter((x) => typeof x === "string") : [];
|
|
38259
39461
|
} catch {
|
|
38260
39462
|
return [];
|
|
@@ -38514,8 +39716,8 @@ function TranscriptLine({ line }) {
|
|
|
38514
39716
|
var import_react24 = __toESM(require_react(), 1);
|
|
38515
39717
|
|
|
38516
39718
|
// src/ui/slash-commands.ts
|
|
38517
|
-
import { existsSync as
|
|
38518
|
-
import { join as
|
|
39719
|
+
import { existsSync as existsSync12 } from "fs";
|
|
39720
|
+
import { join as join22 } from "path";
|
|
38519
39721
|
var CONFIG_KEYS = [
|
|
38520
39722
|
"aiCli",
|
|
38521
39723
|
"claudeHome",
|
|
@@ -38527,7 +39729,7 @@ var CONFIG_KEYS = [
|
|
|
38527
39729
|
"aiEnv"
|
|
38528
39730
|
];
|
|
38529
39731
|
function configPath(profileDir2) {
|
|
38530
|
-
return
|
|
39732
|
+
return join22(profileDir2, "config.json");
|
|
38531
39733
|
}
|
|
38532
39734
|
function parseConfigAssignment(key, valueTokens) {
|
|
38533
39735
|
const value = valueTokens.join(" ").trim();
|
|
@@ -38577,7 +39779,7 @@ function runConfig(ctx) {
|
|
|
38577
39779
|
ctx.print(`\u2714 ${verb} default config: ${path}`);
|
|
38578
39780
|
ctx.print("add claude profiles: /config set claudeHome .claude .claude-1 (tried in order)");
|
|
38579
39781
|
};
|
|
38580
|
-
if (
|
|
39782
|
+
if (existsSync12(path)) {
|
|
38581
39783
|
ctx.confirm(
|
|
38582
39784
|
`config exists: ${path} \u2014 replace it with defaults? (y/N)`,
|
|
38583
39785
|
() => writeDefaults("replaced with")
|
|
@@ -38662,7 +39864,7 @@ function runOutput(ctx) {
|
|
|
38662
39864
|
}
|
|
38663
39865
|
function runLogs(ctx) {
|
|
38664
39866
|
const n = Math.max(1, Math.min(200, Number(ctx.args[0]) || 20));
|
|
38665
|
-
const dir2 =
|
|
39867
|
+
const dir2 = join22(ctx.info.profileDir, "logs");
|
|
38666
39868
|
const lines = readRecentLogLines(dir2, n);
|
|
38667
39869
|
if (lines.length === 0) {
|
|
38668
39870
|
ctx.print("No log lines yet.");
|
|
@@ -38986,11 +40188,29 @@ function App2({
|
|
|
38986
40188
|
const [history, setHistory] = (0, import_react26.useState)(() => loadInputHistory(info.profileDir));
|
|
38987
40189
|
const [startedAt] = (0, import_react26.useState)(() => Date.now());
|
|
38988
40190
|
const [pendingConfirm, setPendingConfirm] = (0, import_react26.useState)(null);
|
|
40191
|
+
const [autoClearIdleMs] = (0, import_react26.useState)(() => {
|
|
40192
|
+
const min = readProfileConfig(info.profileDir).autoClearIdleMinutes ?? 10;
|
|
40193
|
+
return min > 0 ? min * 6e4 : 0;
|
|
40194
|
+
});
|
|
40195
|
+
const lastActivityRef = (0, import_react26.useRef)(startedAt);
|
|
40196
|
+
const busyRef = (0, import_react26.useRef)(busy);
|
|
40197
|
+
busyRef.current = busy;
|
|
40198
|
+
const entriesLenRef = (0, import_react26.useRef)(entries2.length);
|
|
40199
|
+
entriesLenRef.current = entries2.length;
|
|
40200
|
+
const pendingConfirmRef = (0, import_react26.useRef)(pendingConfirm);
|
|
40201
|
+
pendingConfirmRef.current = pendingConfirm;
|
|
40202
|
+
const clearTranscript = (0, import_react26.useCallback)(() => {
|
|
40203
|
+
setEntries([]);
|
|
40204
|
+
setScrollBack(0);
|
|
40205
|
+
stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
40206
|
+
bus.clear();
|
|
40207
|
+
}, [stdout, bus]);
|
|
38989
40208
|
(0, import_react26.useEffect)(() => {
|
|
38990
40209
|
const offTranscript = bus.onTranscript((entry) => {
|
|
38991
40210
|
setEntries(
|
|
38992
40211
|
(prev) => prev.length >= ENTRY_CAP ? [...prev.slice(prev.length - ENTRY_CAP + 1), entry] : [...prev, entry]
|
|
38993
40212
|
);
|
|
40213
|
+
if (entry.source !== "system") lastActivityRef.current = Date.now();
|
|
38994
40214
|
if (entry.kind === "aireq" || entry.kind === "aires") setExpanded(/* @__PURE__ */ new Set());
|
|
38995
40215
|
});
|
|
38996
40216
|
const offUpdate = bus.onTranscriptUpdate(
|
|
@@ -39017,6 +40237,21 @@ function App2({
|
|
|
39017
40237
|
offTokens();
|
|
39018
40238
|
};
|
|
39019
40239
|
}, [bus]);
|
|
40240
|
+
(0, import_react26.useEffect)(() => {
|
|
40241
|
+
if (autoClearIdleMs <= 0) return;
|
|
40242
|
+
const timer = setInterval(() => {
|
|
40243
|
+
if (busyRef.current !== null || pendingConfirmRef.current || entriesLenRef.current === 0) return;
|
|
40244
|
+
if (Date.now() - lastActivityRef.current < autoClearIdleMs) return;
|
|
40245
|
+
clearTranscript();
|
|
40246
|
+
bus.push({
|
|
40247
|
+
source: "system",
|
|
40248
|
+
kind: "log",
|
|
40249
|
+
text: `Transcript auto-cleared after ${Math.round(autoClearIdleMs / 6e4)} min idle.`
|
|
40250
|
+
});
|
|
40251
|
+
lastActivityRef.current = Date.now();
|
|
40252
|
+
}, 6e4);
|
|
40253
|
+
return () => clearInterval(timer);
|
|
40254
|
+
}, [autoClearIdleMs, bus, clearTranscript]);
|
|
39020
40255
|
const bodyHeight = Math.max(3, rows - CHROME_ROWS);
|
|
39021
40256
|
const textWidth = Math.max(10, cols - 1 - 8);
|
|
39022
40257
|
const lines = (0, import_react26.useMemo)(() => flattenEntries(entries2, textWidth, expanded), [entries2, textWidth, expanded]);
|
|
@@ -39042,6 +40277,7 @@ function App2({
|
|
|
39042
40277
|
};
|
|
39043
40278
|
const handleSubmit = (input) => {
|
|
39044
40279
|
setScrollBack(0);
|
|
40280
|
+
lastActivityRef.current = Date.now();
|
|
39045
40281
|
const trimmed = input.trim();
|
|
39046
40282
|
if (pendingConfirm) {
|
|
39047
40283
|
const { onYes } = pendingConfirm;
|
|
@@ -39057,11 +40293,7 @@ function App2({
|
|
|
39057
40293
|
runSlashCommand(trimmed, {
|
|
39058
40294
|
info,
|
|
39059
40295
|
print: (text, level) => bus.push({ source: "local", kind: "log", text, level }),
|
|
39060
|
-
clear:
|
|
39061
|
-
setEntries([]);
|
|
39062
|
-
setScrollBack(0);
|
|
39063
|
-
stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
39064
|
-
},
|
|
40296
|
+
clear: clearTranscript,
|
|
39065
40297
|
quit: () => exit(),
|
|
39066
40298
|
confirm: (prompt, onYes) => {
|
|
39067
40299
|
bus.push({ source: "local", kind: "log", text: prompt });
|
|
@@ -39133,7 +40365,7 @@ async function runStart(profileDir2, profileName) {
|
|
|
39133
40365
|
return;
|
|
39134
40366
|
}
|
|
39135
40367
|
logger.configure({
|
|
39136
|
-
dir:
|
|
40368
|
+
dir: join23(profileDir2, "logs"),
|
|
39137
40369
|
level: resolveLogLevel(),
|
|
39138
40370
|
base: { version: CLI_VERSION, profile: profileName, scope: credential.scope }
|
|
39139
40371
|
});
|
|
@@ -39153,7 +40385,7 @@ async function runStart(profileDir2, profileName) {
|
|
|
39153
40385
|
if (result.action === "updated") {
|
|
39154
40386
|
logger.info("update.updated", { version: result.version });
|
|
39155
40387
|
console.log("Restarting with the new version\u2026");
|
|
39156
|
-
const child =
|
|
40388
|
+
const child = spawn6(process.execPath, process.argv.slice(1), {
|
|
39157
40389
|
stdio: "inherit",
|
|
39158
40390
|
env: { ...process.env, FOURPM_NO_UPDATE: "1" }
|
|
39159
40391
|
});
|
|
@@ -39191,7 +40423,7 @@ async function runStart(profileDir2, profileName) {
|
|
|
39191
40423
|
logger.error("ws.run.crash", { message: e.message, stack: e.stack });
|
|
39192
40424
|
if (client.isStopped) return;
|
|
39193
40425
|
bus.log(`Recovered from an internal error \u2014 reconnecting: ${e.message}`, "error");
|
|
39194
|
-
await new Promise((
|
|
40426
|
+
await new Promise((resolve8) => setTimeout(resolve8, 1e3));
|
|
39195
40427
|
}
|
|
39196
40428
|
}
|
|
39197
40429
|
};
|
|
@@ -39318,10 +40550,10 @@ function Picker({
|
|
|
39318
40550
|
] });
|
|
39319
40551
|
}
|
|
39320
40552
|
function selectFromList(items, title) {
|
|
39321
|
-
return new Promise((
|
|
40553
|
+
return new Promise((resolve8) => {
|
|
39322
40554
|
let chosen = null;
|
|
39323
40555
|
const app = render_default(/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Picker, { items, title, onDone: (v) => chosen = v }));
|
|
39324
|
-
void app.waitUntilExit().then(() =>
|
|
40556
|
+
void app.waitUntilExit().then(() => resolve8(chosen));
|
|
39325
40557
|
});
|
|
39326
40558
|
}
|
|
39327
40559
|
|