@wrongstack/core 0.9.0 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-bridge-Bad3ZGQ7.d.ts → agent-bridge-DMVOX0cF.d.ts} +1 -1
- package/dist/{agent-subagent-runner-Cc8sRJOF.d.ts → agent-subagent-runner-DaF_EgRG.d.ts} +3 -3
- package/dist/{compactor-Mw7-rNyb.d.ts → compactor-DVTKL7XD.d.ts} +1 -1
- package/dist/{config-Bi4Q0fnz.d.ts → config-SkMIDN9L.d.ts} +22 -2
- package/dist/{context-z2x5gv_V.d.ts → context-BRNbHmRM.d.ts} +6 -0
- package/dist/coordination/index.d.ts +12 -12
- package/dist/coordination/index.js +58 -37
- package/dist/coordination/index.js.map +1 -1
- package/dist/{default-config-DvRSTELf.d.ts → default-config-Uzla85H-.d.ts} +5 -1
- package/dist/defaults/index.d.ts +23 -23
- package/dist/defaults/index.js +102 -72
- package/dist/defaults/index.js.map +1 -1
- package/dist/{events-yCpJD4wa.d.ts → events-CiG9qUM_.d.ts} +1 -1
- package/dist/execution/index.d.ts +14 -14
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/{index-Dl3PaD7f.d.ts → index-Bsha5K4D.d.ts} +8 -8
- package/dist/{index-SxpnwRlJ.d.ts → index-CP8638Wm.d.ts} +5 -5
- package/dist/index.d.ts +148 -39
- package/dist/index.js +1545 -98
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/kernel/index.d.ts +9 -9
- package/dist/{mcp-servers-n2L9ohMX.d.ts → mcp-servers-BouUWYW6.d.ts} +3 -3
- package/dist/models/index.d.ts +2 -2
- package/dist/{multi-agent-C8uY45AY.d.ts → multi-agent-3ZnTB1aT.d.ts} +2 -2
- package/dist/{multi-agent-coordinator-C7YbZUY8.d.ts → multi-agent-coordinator-DTXF2aAl.d.ts} +2 -2
- package/dist/{null-fleet-bus-Bb2jVLKr.d.ts → null-fleet-bus-Chrc_3Pp.d.ts} +6 -6
- package/dist/observability/index.d.ts +2 -2
- package/dist/{path-resolver-DnC1nOjb.d.ts → path-resolver-TcJfc29Y.d.ts} +2 -2
- package/dist/{permission-policy-BBa1M1xc.d.ts → permission-policy-BpCGYBud.d.ts} +2 -2
- package/dist/{plan-templates-Cc-04Z8o.d.ts → plan-templates-envSmNlZ.d.ts} +10 -5
- package/dist/{provider-runner-C4ouU7--.d.ts → provider-runner-BEpikbbN.d.ts} +3 -3
- package/dist/{retry-policy-OwtKNxo8.d.ts → retry-policy-BYkq0ugs.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -8
- package/dist/{secret-scrubber-CyE1-EMG.d.ts → secret-scrubber-DttNiGYA.d.ts} +1 -1
- package/dist/{secret-scrubber-C0n1EqrC.d.ts → secret-scrubber-QSeI0ADi.d.ts} +1 -1
- package/dist/security/index.d.ts +4 -4
- package/dist/security/index.js +5 -2
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-DkvgYVS4.d.ts → selector-pox8abg0.d.ts} +1 -1
- package/dist/{session-reader-DsadjyF9.d.ts → session-reader-CSWcb5Ga.d.ts} +1 -1
- package/dist/storage/index.d.ts +77 -8
- package/dist/storage/index.js +433 -62
- package/dist/storage/index.js.map +1 -1
- package/dist/{system-prompt-CWA6ml-d.d.ts → system-prompt-Bs-Wliab.d.ts} +1 -1
- package/dist/{tool-executor-Ch25BDwa.d.ts → tool-executor-CsktM3h9.d.ts} +4 -4
- package/dist/types/index.d.ts +16 -16
- package/dist/types/index.js +8 -2
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.js +3 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/{wstack-paths-CJYrjQU8.d.ts → wstack-paths-BCgmTNlG.d.ts} +4 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -56,8 +56,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
|
|
|
56
56
|
}
|
|
57
57
|
let mode;
|
|
58
58
|
try {
|
|
59
|
-
const
|
|
60
|
-
mode =
|
|
59
|
+
const stat9 = await fsp2.stat(targetPath);
|
|
60
|
+
mode = stat9.mode & 511;
|
|
61
61
|
} catch {
|
|
62
62
|
mode = opts.mode;
|
|
63
63
|
}
|
|
@@ -1021,6 +1021,9 @@ var DEFAULT_CONTEXT_CONFIG = Object.freeze({
|
|
|
1021
1021
|
preserveK: 10,
|
|
1022
1022
|
eliseThreshold: 2e3
|
|
1023
1023
|
});
|
|
1024
|
+
var DEFAULT_AUTONOMY_CONFIG = Object.freeze({
|
|
1025
|
+
autoProceedDelayMs: 45e3
|
|
1026
|
+
});
|
|
1024
1027
|
|
|
1025
1028
|
// src/types/secret-vault.ts
|
|
1026
1029
|
var ENCRYPTED_PREFIX = "enc:v1:";
|
|
@@ -2061,7 +2064,10 @@ var PATTERNS = [
|
|
|
2061
2064
|
type: "bearer_token",
|
|
2062
2065
|
// Anchored with alternation instead of negative lookahead — avoids V8
|
|
2063
2066
|
// backtracking risk on adversarial input. Bounded at 512 chars.
|
|
2064
|
-
|
|
2067
|
+
// Min 12 chars: some OAuth providers issue shorter-lived tokens (< 20
|
|
2068
|
+
// chars). A 12-char base64 string has ~71 bits of entropy — above the
|
|
2069
|
+
// threshold where random strings are unlikely to produce false matches.
|
|
2070
|
+
regex: /(?:^|[^A-Za-z0-9_.~+/-])Bearer\s+[A-Za-z0-9._~+/-]{12,512}=*(?:$|[^A-Za-z0-9_.~+/-])/g
|
|
2065
2071
|
},
|
|
2066
2072
|
{
|
|
2067
2073
|
type: "high_entropy_env",
|
|
@@ -3910,6 +3916,7 @@ function resolveWstackPaths(opts) {
|
|
|
3910
3916
|
secretsKey: path6.join(globalRoot, ".key"),
|
|
3911
3917
|
globalMemory: path6.join(globalRoot, "memory.md"),
|
|
3912
3918
|
globalSkills: path6.join(globalRoot, "skills"),
|
|
3919
|
+
globalPrompts: path6.join(globalRoot, "prompts"),
|
|
3913
3920
|
cacheDir: path6.join(globalRoot, "cache"),
|
|
3914
3921
|
modelsCache: path6.join(globalRoot, "cache", "models.dev.json"),
|
|
3915
3922
|
historyFile: path6.join(globalRoot, "history"),
|
|
@@ -3929,7 +3936,8 @@ function resolveWstackPaths(opts) {
|
|
|
3929
3936
|
projectTaskGraphs: path6.join(projectDir, "task-graphs"),
|
|
3930
3937
|
projectSddSession: path6.join(projectDir, "sdd-session.json"),
|
|
3931
3938
|
projectPlan: path6.join(projectDir, "plan.json"),
|
|
3932
|
-
projectAutophase: path6.join(projectDir, "autophase")
|
|
3939
|
+
projectAutophase: path6.join(projectDir, "autophase"),
|
|
3940
|
+
syncConfig: path6.join(globalRoot, "sync.json")
|
|
3933
3941
|
};
|
|
3934
3942
|
}
|
|
3935
3943
|
|
|
@@ -4028,11 +4036,11 @@ function validateAgainstSchema(value, schema) {
|
|
|
4028
4036
|
walk2(value, schema, "", errors);
|
|
4029
4037
|
return { ok: errors.length === 0, errors };
|
|
4030
4038
|
}
|
|
4031
|
-
function walk2(value, schema,
|
|
4039
|
+
function walk2(value, schema, path31, errors) {
|
|
4032
4040
|
if (schema.enum !== void 0) {
|
|
4033
4041
|
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
4034
4042
|
errors.push({
|
|
4035
|
-
path:
|
|
4043
|
+
path: path31 || "<root>",
|
|
4036
4044
|
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
4037
4045
|
});
|
|
4038
4046
|
return;
|
|
@@ -4041,7 +4049,7 @@ function walk2(value, schema, path28, errors) {
|
|
|
4041
4049
|
if (typeof schema.type === "string") {
|
|
4042
4050
|
if (!checkType(value, schema.type)) {
|
|
4043
4051
|
errors.push({
|
|
4044
|
-
path:
|
|
4052
|
+
path: path31 || "<root>",
|
|
4045
4053
|
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
4046
4054
|
});
|
|
4047
4055
|
return;
|
|
@@ -4051,19 +4059,19 @@ function walk2(value, schema, path28, errors) {
|
|
|
4051
4059
|
const obj = value;
|
|
4052
4060
|
for (const req of schema.required ?? []) {
|
|
4053
4061
|
if (!(req in obj)) {
|
|
4054
|
-
errors.push({ path: joinPath(
|
|
4062
|
+
errors.push({ path: joinPath(path31, req), message: "required property missing" });
|
|
4055
4063
|
}
|
|
4056
4064
|
}
|
|
4057
4065
|
if (schema.properties) {
|
|
4058
4066
|
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
4059
4067
|
if (key in obj) {
|
|
4060
|
-
walk2(obj[key], subSchema, joinPath(
|
|
4068
|
+
walk2(obj[key], subSchema, joinPath(path31, key), errors);
|
|
4061
4069
|
}
|
|
4062
4070
|
}
|
|
4063
4071
|
}
|
|
4064
4072
|
}
|
|
4065
4073
|
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
4066
|
-
value.forEach((item, i) => walk2(item, schema.items, `${
|
|
4074
|
+
value.forEach((item, i) => walk2(item, schema.items, `${path31}[${i}]`, errors));
|
|
4067
4075
|
}
|
|
4068
4076
|
}
|
|
4069
4077
|
function checkType(value, type) {
|
|
@@ -4125,24 +4133,30 @@ var DefaultSessionStore = class {
|
|
|
4125
4133
|
this.dir = opts.dir;
|
|
4126
4134
|
this.events = opts.events;
|
|
4127
4135
|
}
|
|
4128
|
-
|
|
4136
|
+
/** Join session ID to its absolute path within the store directory. */
|
|
4137
|
+
sessionPath(id, ext) {
|
|
4138
|
+
return path6.join(this.dir, `${id}${ext}`);
|
|
4139
|
+
}
|
|
4140
|
+
async ensureShardDir(_id) {
|
|
4129
4141
|
await ensureDir(this.dir);
|
|
4142
|
+
return this.dir;
|
|
4143
|
+
}
|
|
4144
|
+
async create(meta) {
|
|
4130
4145
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4131
4146
|
const id = meta.id ?? `${startedAt.replace(/[:.]/g, "-")}-${randomBytes(2).toString("hex")}`;
|
|
4132
|
-
const
|
|
4147
|
+
const shardDir = await this.ensureShardDir(id);
|
|
4148
|
+
const file = path6.join(shardDir, `${id}.jsonl`);
|
|
4133
4149
|
let handle;
|
|
4134
4150
|
try {
|
|
4135
4151
|
handle = await fsp2.open(file, "a", 384);
|
|
4136
4152
|
} catch (err) {
|
|
4137
4153
|
throw new Error(
|
|
4138
4154
|
`Failed to open session file: ${err instanceof Error ? err.message : String(err)}`,
|
|
4139
|
-
{
|
|
4140
|
-
cause: err
|
|
4141
|
-
}
|
|
4155
|
+
{ cause: err }
|
|
4142
4156
|
);
|
|
4143
4157
|
}
|
|
4144
4158
|
try {
|
|
4145
|
-
return new FileSessionWriter(id, handle, startedAt, meta, this.events, { dir:
|
|
4159
|
+
return new FileSessionWriter(id, handle, startedAt, meta, this.events, { dir: shardDir, filePath: file });
|
|
4146
4160
|
} catch (err) {
|
|
4147
4161
|
await handle.close().catch(() => {
|
|
4148
4162
|
});
|
|
@@ -4150,15 +4164,7 @@ var DefaultSessionStore = class {
|
|
|
4150
4164
|
}
|
|
4151
4165
|
}
|
|
4152
4166
|
async resume(id) {
|
|
4153
|
-
const file =
|
|
4154
|
-
try {
|
|
4155
|
-
await fsp2.access(file, fsp2.constants.R_OK);
|
|
4156
|
-
} catch {
|
|
4157
|
-
throw new Error(
|
|
4158
|
-
`Session "${id}" not found \u2014 the session file does not exist or was deleted.`,
|
|
4159
|
-
{ cause: new Error("ENOENT") }
|
|
4160
|
-
);
|
|
4161
|
-
}
|
|
4167
|
+
const file = this.sessionPath(id, ".jsonl");
|
|
4162
4168
|
const data = await this.load(id);
|
|
4163
4169
|
let handle;
|
|
4164
4170
|
try {
|
|
@@ -4190,7 +4196,7 @@ var DefaultSessionStore = class {
|
|
|
4190
4196
|
}
|
|
4191
4197
|
}
|
|
4192
4198
|
async load(id) {
|
|
4193
|
-
const file =
|
|
4199
|
+
const file = this.sessionPath(id, ".jsonl");
|
|
4194
4200
|
const raw = await fsp2.readFile(file, "utf8");
|
|
4195
4201
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
4196
4202
|
const events = [];
|
|
@@ -4210,8 +4216,7 @@ var DefaultSessionStore = class {
|
|
|
4210
4216
|
async list(limit = 20) {
|
|
4211
4217
|
try {
|
|
4212
4218
|
await ensureDir(this.dir);
|
|
4213
|
-
const
|
|
4214
|
-
const ids = files.filter((f) => f.endsWith(".jsonl")).map((f) => f.replace(/\.jsonl$/, ""));
|
|
4219
|
+
const ids = await this.collectSessionIds(this.dir);
|
|
4215
4220
|
const sessions = await Promise.all(ids.map((id) => this.summaryFor(id).catch(() => null)));
|
|
4216
4221
|
const out = sessions.filter((s) => s !== null);
|
|
4217
4222
|
out.sort((a, b) => {
|
|
@@ -4224,15 +4229,29 @@ var DefaultSessionStore = class {
|
|
|
4224
4229
|
return [];
|
|
4225
4230
|
}
|
|
4226
4231
|
}
|
|
4232
|
+
/** Recursively collect all session IDs from shard subdirectories. */
|
|
4233
|
+
async collectSessionIds(dir) {
|
|
4234
|
+
const ids = [];
|
|
4235
|
+
const entries = await fsp2.readdir(dir, { withFileTypes: true });
|
|
4236
|
+
for (const entry of entries) {
|
|
4237
|
+
const full = path6.join(dir, entry.name);
|
|
4238
|
+
if (entry.isDirectory()) {
|
|
4239
|
+
ids.push(...await this.collectSessionIds(full));
|
|
4240
|
+
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
4241
|
+
ids.push(entry.name.replace(/\.jsonl$/, ""));
|
|
4242
|
+
}
|
|
4243
|
+
}
|
|
4244
|
+
return ids;
|
|
4245
|
+
}
|
|
4227
4246
|
async summaryFor(id) {
|
|
4228
|
-
const manifest =
|
|
4247
|
+
const manifest = this.sessionPath(id, ".summary.json");
|
|
4229
4248
|
try {
|
|
4230
4249
|
const raw = await fsp2.readFile(manifest, "utf8");
|
|
4231
4250
|
return JSON.parse(raw);
|
|
4232
4251
|
} catch {
|
|
4233
|
-
const full =
|
|
4234
|
-
const
|
|
4235
|
-
const summary = await this.summarize(id,
|
|
4252
|
+
const full = this.sessionPath(id, ".jsonl");
|
|
4253
|
+
const stat9 = await fsp2.stat(full);
|
|
4254
|
+
const summary = await this.summarize(id, stat9.mtime.toISOString());
|
|
4236
4255
|
await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
4237
4256
|
console.warn(
|
|
4238
4257
|
`[session-store] Failed to write manifest for "${id}":`,
|
|
@@ -4243,12 +4262,13 @@ var DefaultSessionStore = class {
|
|
|
4243
4262
|
}
|
|
4244
4263
|
}
|
|
4245
4264
|
async delete(id) {
|
|
4246
|
-
await fsp2.unlink(
|
|
4247
|
-
await fsp2.unlink(
|
|
4265
|
+
await fsp2.unlink(this.sessionPath(id, ".jsonl"));
|
|
4266
|
+
await fsp2.unlink(this.sessionPath(id, ".summary.json")).catch(() => void 0);
|
|
4248
4267
|
}
|
|
4249
4268
|
async clearHistory(id) {
|
|
4250
|
-
|
|
4251
|
-
const
|
|
4269
|
+
await this.ensureShardDir(id);
|
|
4270
|
+
const file = this.sessionPath(id, ".jsonl");
|
|
4271
|
+
const meta = this.sessionPath(id, ".summary.json");
|
|
4252
4272
|
const record = `${JSON.stringify({
|
|
4253
4273
|
type: "session_start",
|
|
4254
4274
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -4292,7 +4312,8 @@ var DefaultSessionStore = class {
|
|
|
4292
4312
|
startedAt: start?.ts ?? (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
4293
4313
|
endedAt: end?.ts,
|
|
4294
4314
|
model: start?.model,
|
|
4295
|
-
provider: start?.provider
|
|
4315
|
+
provider: start?.provider,
|
|
4316
|
+
pendingToolUses: end?.pendingToolUses
|
|
4296
4317
|
};
|
|
4297
4318
|
}
|
|
4298
4319
|
replay(events, sessionId = "unknown") {
|
|
@@ -4392,9 +4413,6 @@ var FileSessionWriter = class {
|
|
|
4392
4413
|
tokenIn = 0;
|
|
4393
4414
|
tokenOut = 0;
|
|
4394
4415
|
filePath;
|
|
4395
|
-
/** Public accessor for the JSONL path — required by SessionWriter so
|
|
4396
|
-
* observability surfaces (`/fleet log`, FleetPanel) can locate the
|
|
4397
|
-
* transcript without recomputing the path from session metadata. */
|
|
4398
4416
|
get transcriptPath() {
|
|
4399
4417
|
return this.filePath || void 0;
|
|
4400
4418
|
}
|
|
@@ -4402,13 +4420,16 @@ var FileSessionWriter = class {
|
|
|
4402
4420
|
resumed;
|
|
4403
4421
|
appendFailCount = 0;
|
|
4404
4422
|
lastAppendWarnAt = 0;
|
|
4405
|
-
// Rewind support: track pending file changes and prompt index
|
|
4406
4423
|
promptIndex = 0;
|
|
4407
4424
|
pendingFileSnapshots = [];
|
|
4408
|
-
|
|
4425
|
+
/** Tracks open tool_use IDs during the current run to serialize on close for resume. */
|
|
4426
|
+
openToolUses = /* @__PURE__ */ new Set();
|
|
4409
4427
|
recordFileChange(input) {
|
|
4410
4428
|
this.pendingFileSnapshots.push(input);
|
|
4411
4429
|
}
|
|
4430
|
+
get pendingToolUses() {
|
|
4431
|
+
return Array.from(this.openToolUses);
|
|
4432
|
+
}
|
|
4412
4433
|
async writeSessionStartLazy() {
|
|
4413
4434
|
const record = `${JSON.stringify({
|
|
4414
4435
|
type: this.resumed ? "session_resumed" : "session_start",
|
|
@@ -4451,13 +4472,12 @@ var FileSessionWriter = class {
|
|
|
4451
4472
|
}
|
|
4452
4473
|
}
|
|
4453
4474
|
}
|
|
4454
|
-
/**
|
|
4455
|
-
* Watch events as they're appended and keep the summary state hot, so
|
|
4456
|
-
* `close()` can flush a `<id>.summary.json` manifest without re-reading
|
|
4457
|
-
* the JSONL. `list()` reads only manifests, turning a per-session full
|
|
4458
|
-
* parse into a single stat+read.
|
|
4459
|
-
*/
|
|
4460
4475
|
observeForSummary(event) {
|
|
4476
|
+
if (event.type === "tool_use") {
|
|
4477
|
+
this.openToolUses.add(event.id);
|
|
4478
|
+
} else if (event.type === "tool_result") {
|
|
4479
|
+
this.openToolUses.delete(event.id);
|
|
4480
|
+
}
|
|
4461
4481
|
if (event.type === "user_input" && this.summary.title === "(empty session)") {
|
|
4462
4482
|
this.summary = { ...this.summary, title: userInputTitle(event.content) };
|
|
4463
4483
|
} else if (event.type === "llm_response") {
|
|
@@ -4551,7 +4571,16 @@ var FileSessionWriter = class {
|
|
|
4551
4571
|
}
|
|
4552
4572
|
}
|
|
4553
4573
|
const truncated = kept.join("\n");
|
|
4554
|
-
|
|
4574
|
+
const tmpPath = `${this.filePath}.rewind.tmp`;
|
|
4575
|
+
await fsp2.writeFile(tmpPath, truncated + "\n", "utf8");
|
|
4576
|
+
try {
|
|
4577
|
+
await this.handle.close();
|
|
4578
|
+
await fsp2.rename(tmpPath, this.filePath);
|
|
4579
|
+
this.handle = await fsp2.open(this.filePath, "a", 384);
|
|
4580
|
+
} catch (err) {
|
|
4581
|
+
await fsp2.unlink(tmpPath).catch(() => void 0);
|
|
4582
|
+
throw err;
|
|
4583
|
+
}
|
|
4555
4584
|
await this.append({
|
|
4556
4585
|
type: "rewound",
|
|
4557
4586
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -5244,7 +5273,10 @@ var DefaultConfigLoader = class {
|
|
|
5244
5273
|
}
|
|
5245
5274
|
if (c.mode !== void 0 && !isContextWindowModeId(c.mode)) {
|
|
5246
5275
|
const known = listContextWindowModes().map((m) => m.id).join(", ");
|
|
5247
|
-
|
|
5276
|
+
console.warn(
|
|
5277
|
+
`[config] Ignoring unknown context.mode "${c.mode}" (expected one of: ${known}); falling back to "${DEFAULT_CONTEXT_WINDOW_MODE_ID}".`
|
|
5278
|
+
);
|
|
5279
|
+
c.mode = DEFAULT_CONTEXT_WINDOW_MODE_ID;
|
|
5248
5280
|
}
|
|
5249
5281
|
}
|
|
5250
5282
|
validateIdentity(cfg) {
|
|
@@ -6165,7 +6197,7 @@ var DefaultPermissionPolicy = class {
|
|
|
6165
6197
|
return { permission: "auto", source: "context", reason: "file already read in this session" };
|
|
6166
6198
|
}
|
|
6167
6199
|
}
|
|
6168
|
-
if (tool.permission === "auto") {
|
|
6200
|
+
if (tool.permission === "auto" && !tool.mutating) {
|
|
6169
6201
|
return { permission: "auto", source: "default" };
|
|
6170
6202
|
}
|
|
6171
6203
|
if (this.promptDelegate) {
|
|
@@ -8335,8 +8367,8 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
8335
8367
|
await saveGoal(this.goalPath, abandoned);
|
|
8336
8368
|
}
|
|
8337
8369
|
try {
|
|
8338
|
-
const { unlink:
|
|
8339
|
-
await
|
|
8370
|
+
const { unlink: unlink13 } = await import('fs/promises');
|
|
8371
|
+
await unlink13(this.goalPath);
|
|
8340
8372
|
} catch {
|
|
8341
8373
|
}
|
|
8342
8374
|
this.opts.onEternalStop?.();
|
|
@@ -14317,8 +14349,8 @@ async function loadProjectModes(modesDir) {
|
|
|
14317
14349
|
for (const entry of entries) {
|
|
14318
14350
|
if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
|
|
14319
14351
|
const filePath = path6.join(modesDir, entry);
|
|
14320
|
-
const
|
|
14321
|
-
if (!
|
|
14352
|
+
const stat9 = await fsp2.stat(filePath);
|
|
14353
|
+
if (!stat9.isFile()) continue;
|
|
14322
14354
|
const content = await fsp2.readFile(filePath, "utf8");
|
|
14323
14355
|
const id = path6.basename(entry, path6.extname(entry));
|
|
14324
14356
|
modes.push({
|
|
@@ -15592,9 +15624,9 @@ var AISpecBuilder = class {
|
|
|
15592
15624
|
if (!this.sessionPath) return;
|
|
15593
15625
|
try {
|
|
15594
15626
|
const fsp18 = await import('fs/promises');
|
|
15595
|
-
const
|
|
15627
|
+
const path31 = await import('path');
|
|
15596
15628
|
const { atomicWrite: atomicWrite2 } = await Promise.resolve().then(() => (init_atomic_write(), atomic_write_exports));
|
|
15597
|
-
await fsp18.mkdir(
|
|
15629
|
+
await fsp18.mkdir(path31.dirname(this.sessionPath), { recursive: true });
|
|
15598
15630
|
await atomicWrite2(this.sessionPath, JSON.stringify(this.session, null, 2));
|
|
15599
15631
|
} catch {
|
|
15600
15632
|
}
|
|
@@ -16304,15 +16336,15 @@ function computeCriticalPath(graph, topoOrder, blockedByMap) {
|
|
|
16304
16336
|
maxId = id;
|
|
16305
16337
|
}
|
|
16306
16338
|
}
|
|
16307
|
-
const
|
|
16339
|
+
const path31 = [];
|
|
16308
16340
|
let current = maxId;
|
|
16309
16341
|
const visited = /* @__PURE__ */ new Set();
|
|
16310
16342
|
while (current && !visited.has(current)) {
|
|
16311
16343
|
visited.add(current);
|
|
16312
|
-
|
|
16344
|
+
path31.unshift(current);
|
|
16313
16345
|
current = prev.get(current) ?? null;
|
|
16314
16346
|
}
|
|
16315
|
-
return
|
|
16347
|
+
return path31;
|
|
16316
16348
|
}
|
|
16317
16349
|
function computeParallelGroups(graph, blockedByMap) {
|
|
16318
16350
|
const groups = [];
|
|
@@ -17292,7 +17324,7 @@ async function startMetricsServer(opts) {
|
|
|
17292
17324
|
const tls = opts.tls;
|
|
17293
17325
|
const useHttps = !!(tls?.cert && tls?.key);
|
|
17294
17326
|
const host = opts.host ?? "127.0.0.1";
|
|
17295
|
-
const
|
|
17327
|
+
const path31 = opts.path ?? "/metrics";
|
|
17296
17328
|
const healthPath = opts.healthPath ?? "/healthz";
|
|
17297
17329
|
const healthRegistry = opts.healthRegistry;
|
|
17298
17330
|
const listener = (req, res) => {
|
|
@@ -17302,7 +17334,7 @@ async function startMetricsServer(opts) {
|
|
|
17302
17334
|
return;
|
|
17303
17335
|
}
|
|
17304
17336
|
const url = req.url.split("?")[0];
|
|
17305
|
-
if (url ===
|
|
17337
|
+
if (url === path31) {
|
|
17306
17338
|
let body;
|
|
17307
17339
|
try {
|
|
17308
17340
|
body = renderPrometheus(opts.sink.snapshot());
|
|
@@ -17366,7 +17398,7 @@ async function startMetricsServer(opts) {
|
|
|
17366
17398
|
const protocol = useHttps ? "https" : "http";
|
|
17367
17399
|
return {
|
|
17368
17400
|
port: boundPort,
|
|
17369
|
-
url: `${protocol}://${host}:${boundPort}${
|
|
17401
|
+
url: `${protocol}://${host}:${boundPort}${path31}`,
|
|
17370
17402
|
close: () => new Promise((resolve9, reject) => {
|
|
17371
17403
|
server.close((err) => err ? reject(err) : resolve9());
|
|
17372
17404
|
})
|
|
@@ -18213,10 +18245,10 @@ var SkillInstaller = class {
|
|
|
18213
18245
|
if (!resolved.startsWith(path6.resolve(destDir))) {
|
|
18214
18246
|
throw new Error(`Path traversal detected in skill file: ${file}`);
|
|
18215
18247
|
}
|
|
18216
|
-
const
|
|
18217
|
-
if (
|
|
18248
|
+
const stat9 = await fsp2.stat(srcPath);
|
|
18249
|
+
if (stat9.size > MAX_SKILL_FILE_SIZE) {
|
|
18218
18250
|
throw new Error(
|
|
18219
|
-
`Skill file "${file}" is too large (${(
|
|
18251
|
+
`Skill file "${file}" is too large (${(stat9.size / 1024).toFixed(1)}KB). Max: ${MAX_SKILL_FILE_SIZE / 1024}KB`
|
|
18220
18252
|
);
|
|
18221
18253
|
}
|
|
18222
18254
|
await fsp2.mkdir(path6.dirname(destPath), { recursive: true });
|
|
@@ -18624,6 +18656,356 @@ async function revertSnapshots(snapshots, projectRoot) {
|
|
|
18624
18656
|
}
|
|
18625
18657
|
return { revertedFiles, errors };
|
|
18626
18658
|
}
|
|
18659
|
+
|
|
18660
|
+
// src/storage/prompt-store.ts
|
|
18661
|
+
init_atomic_write();
|
|
18662
|
+
var DefaultPromptStore = class {
|
|
18663
|
+
dir;
|
|
18664
|
+
constructor(paths) {
|
|
18665
|
+
this.dir = paths.globalPrompts;
|
|
18666
|
+
}
|
|
18667
|
+
async list() {
|
|
18668
|
+
await ensureDir(this.dir);
|
|
18669
|
+
let entries = [];
|
|
18670
|
+
try {
|
|
18671
|
+
const files = await fsp2.readdir(this.dir);
|
|
18672
|
+
for (const file of files) {
|
|
18673
|
+
if (!file.endsWith(".json")) continue;
|
|
18674
|
+
try {
|
|
18675
|
+
const raw = JSON.parse(
|
|
18676
|
+
await fsp2.readFile(path6.join(this.dir, file), "utf8")
|
|
18677
|
+
);
|
|
18678
|
+
entries.push(raw.entry);
|
|
18679
|
+
} catch {
|
|
18680
|
+
}
|
|
18681
|
+
}
|
|
18682
|
+
} catch {
|
|
18683
|
+
}
|
|
18684
|
+
return entries.sort(
|
|
18685
|
+
(a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
|
18686
|
+
);
|
|
18687
|
+
}
|
|
18688
|
+
async get(id) {
|
|
18689
|
+
const file = path6.join(this.dir, `${id}.json`);
|
|
18690
|
+
try {
|
|
18691
|
+
const raw = JSON.parse(await fsp2.readFile(file, "utf8"));
|
|
18692
|
+
return raw.entry;
|
|
18693
|
+
} catch {
|
|
18694
|
+
return null;
|
|
18695
|
+
}
|
|
18696
|
+
}
|
|
18697
|
+
async save(entry) {
|
|
18698
|
+
await ensureDir(this.dir);
|
|
18699
|
+
const file = path6.join(this.dir, `${entry.id}.json`);
|
|
18700
|
+
const raw = { version: 1, entry };
|
|
18701
|
+
await atomicWrite(file, JSON.stringify(raw, null, 2));
|
|
18702
|
+
}
|
|
18703
|
+
async delete(id) {
|
|
18704
|
+
const file = path6.join(this.dir, `${id}.json`);
|
|
18705
|
+
try {
|
|
18706
|
+
await fsp2.unlink(file);
|
|
18707
|
+
return true;
|
|
18708
|
+
} catch {
|
|
18709
|
+
return false;
|
|
18710
|
+
}
|
|
18711
|
+
}
|
|
18712
|
+
async find(query) {
|
|
18713
|
+
const all = await this.list();
|
|
18714
|
+
const lower = query.toLowerCase();
|
|
18715
|
+
return all.filter(
|
|
18716
|
+
(e) => e.title.toLowerCase().includes(lower) || e.content.toLowerCase().includes(lower) || e.tags.some((t2) => t2.toLowerCase().includes(lower))
|
|
18717
|
+
);
|
|
18718
|
+
}
|
|
18719
|
+
/** Create a new entry and return it. Does NOT persist — call save() afterwards. */
|
|
18720
|
+
createNew(title, content, tags = []) {
|
|
18721
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18722
|
+
return {
|
|
18723
|
+
id: randomUUID().slice(0, 8),
|
|
18724
|
+
title,
|
|
18725
|
+
content,
|
|
18726
|
+
tags,
|
|
18727
|
+
createdAt: now,
|
|
18728
|
+
updatedAt: now
|
|
18729
|
+
};
|
|
18730
|
+
}
|
|
18731
|
+
};
|
|
18732
|
+
var ALL_SYNC_CATEGORIES = ["settings", "skills", "prompts", "memory", "history"];
|
|
18733
|
+
var CloudSync = class {
|
|
18734
|
+
constructor(paths, getConfig, setConfig) {
|
|
18735
|
+
this.paths = paths;
|
|
18736
|
+
this.getConfig = getConfig;
|
|
18737
|
+
this.setConfig = setConfig;
|
|
18738
|
+
this.statePath = path6.join(paths.globalRoot, "sync-state.json");
|
|
18739
|
+
}
|
|
18740
|
+
paths;
|
|
18741
|
+
getConfig;
|
|
18742
|
+
setConfig;
|
|
18743
|
+
statePath;
|
|
18744
|
+
state = null;
|
|
18745
|
+
// ── Public API ─────────────────────────────────────────────────────
|
|
18746
|
+
async status() {
|
|
18747
|
+
const cfg = this.getConfig();
|
|
18748
|
+
if (!cfg?.enabled) {
|
|
18749
|
+
return "CloudSync: disabled. Run `/sync enable` to activate.";
|
|
18750
|
+
}
|
|
18751
|
+
const last = this.state?.lastSyncedAt;
|
|
18752
|
+
const since = last ? timeAgo(last) : "never";
|
|
18753
|
+
return [
|
|
18754
|
+
`CloudSync: enabled`,
|
|
18755
|
+
` repo: ${cfg.repo}`,
|
|
18756
|
+
` categories: ${cfg.categories.join(", ")}`,
|
|
18757
|
+
` last sync: ${since}`
|
|
18758
|
+
].join("\n");
|
|
18759
|
+
}
|
|
18760
|
+
async enable(_repo, _categories) {
|
|
18761
|
+
return "Enable via /sync enable.";
|
|
18762
|
+
}
|
|
18763
|
+
async disable() {
|
|
18764
|
+
const cfg = this.getConfig();
|
|
18765
|
+
if (!cfg) return "CloudSync is not configured.";
|
|
18766
|
+
const next = { ...cfg, enabled: false };
|
|
18767
|
+
await this.setConfig(next);
|
|
18768
|
+
return "CloudSync disabled. Local data kept.";
|
|
18769
|
+
}
|
|
18770
|
+
async push(token) {
|
|
18771
|
+
const cfg = this.getConfig();
|
|
18772
|
+
if (!cfg?.enabled) return { ok: false, action: "push", categories: [], message: "Not enabled." };
|
|
18773
|
+
const parts = cfg.repo.split("/");
|
|
18774
|
+
const owner = parts[0];
|
|
18775
|
+
const repoName = parts[1];
|
|
18776
|
+
const branch = "main";
|
|
18777
|
+
const baseTreeSha = this.state?.sha;
|
|
18778
|
+
const { treeEntries, rev } = await this.buildLocalTree(cfg.categories);
|
|
18779
|
+
const newTreeSha = await this.createGitTree(token, owner, repoName, treeEntries, baseTreeSha);
|
|
18780
|
+
const commitSha = await this.createCommit(
|
|
18781
|
+
token,
|
|
18782
|
+
owner,
|
|
18783
|
+
repoName,
|
|
18784
|
+
newTreeSha,
|
|
18785
|
+
baseTreeSha,
|
|
18786
|
+
`Sync ${cfg.categories.join(", ")} \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
18787
|
+
);
|
|
18788
|
+
await this.updateRef(token, owner, repoName, branch, commitSha);
|
|
18789
|
+
const syncState = {
|
|
18790
|
+
version: 1,
|
|
18791
|
+
sha: commitSha,
|
|
18792
|
+
lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18793
|
+
localRev: rev
|
|
18794
|
+
};
|
|
18795
|
+
await fsp2.writeFile(this.statePath, JSON.stringify(syncState, null, 2), "utf8");
|
|
18796
|
+
this.state = syncState;
|
|
18797
|
+
return {
|
|
18798
|
+
ok: true,
|
|
18799
|
+
action: "push",
|
|
18800
|
+
categories: cfg.categories,
|
|
18801
|
+
committedAt: commitSha,
|
|
18802
|
+
message: `Pushed ${cfg.categories.join(", ")} to ${cfg.repo}. Commit: ${commitSha.slice(0, 7)}`
|
|
18803
|
+
};
|
|
18804
|
+
}
|
|
18805
|
+
async pull(token) {
|
|
18806
|
+
const cfg = this.getConfig();
|
|
18807
|
+
if (!cfg?.enabled) return { ok: false, action: "pull", categories: [], message: "Not enabled." };
|
|
18808
|
+
const pullParts = cfg.repo.split("/");
|
|
18809
|
+
const owner = pullParts[0];
|
|
18810
|
+
const repoName = pullParts[1];
|
|
18811
|
+
const branchData = await this.getRef(token, owner, repoName, "main");
|
|
18812
|
+
const currentSha = branchData.object.sha;
|
|
18813
|
+
const commitData = await this.getCommit(token, owner, repoName, currentSha);
|
|
18814
|
+
const treeSha = commitData.tree.sha;
|
|
18815
|
+
const treeEntries = await this.getTreeEntries(token, owner, repoName, treeSha);
|
|
18816
|
+
for (const entry of treeEntries) {
|
|
18817
|
+
if (entry.type !== "blob") continue;
|
|
18818
|
+
const segments = entry.path.split("/");
|
|
18819
|
+
if (segments[0] !== "data" || !segments[1]) continue;
|
|
18820
|
+
const cat = segments[1];
|
|
18821
|
+
if (!["settings", "skills", "prompts", "memory", "history"].includes(cat)) continue;
|
|
18822
|
+
const localPath = this.categoryToPath(cat);
|
|
18823
|
+
if (!localPath) continue;
|
|
18824
|
+
const rel = segments.slice(2).join("/");
|
|
18825
|
+
const destPath = rel ? path6.join(localPath, rel) : localPath;
|
|
18826
|
+
const blobData = await this.getBlob(token, owner, repoName, entry.sha);
|
|
18827
|
+
await fsp2.mkdir(path6.dirname(destPath), { recursive: true });
|
|
18828
|
+
await fsp2.writeFile(destPath, Buffer.from(blobData, "base64"));
|
|
18829
|
+
}
|
|
18830
|
+
const localRev = await this.hashLocalCategories(cfg.categories);
|
|
18831
|
+
const syncState = {
|
|
18832
|
+
version: 1,
|
|
18833
|
+
sha: currentSha,
|
|
18834
|
+
lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18835
|
+
localRev
|
|
18836
|
+
};
|
|
18837
|
+
await fsp2.writeFile(this.statePath, JSON.stringify(syncState, null, 2), "utf8");
|
|
18838
|
+
this.state = syncState;
|
|
18839
|
+
return {
|
|
18840
|
+
ok: true,
|
|
18841
|
+
action: "pull",
|
|
18842
|
+
categories: cfg.categories,
|
|
18843
|
+
committedAt: currentSha,
|
|
18844
|
+
message: `Pulled ${cfg.categories.join(", ")} from ${cfg.repo}. Commit: ${currentSha.slice(0, 7)}`
|
|
18845
|
+
};
|
|
18846
|
+
}
|
|
18847
|
+
async hasLocalChanges() {
|
|
18848
|
+
if (!this.state) return true;
|
|
18849
|
+
const cfg = this.getConfig();
|
|
18850
|
+
if (!cfg) return true;
|
|
18851
|
+
const current = await this.hashLocalCategories(cfg.categories);
|
|
18852
|
+
return current !== this.state.localRev;
|
|
18853
|
+
}
|
|
18854
|
+
async loadState() {
|
|
18855
|
+
try {
|
|
18856
|
+
const raw = await fsp2.readFile(this.statePath, "utf8");
|
|
18857
|
+
this.state = JSON.parse(raw);
|
|
18858
|
+
} catch {
|
|
18859
|
+
this.state = null;
|
|
18860
|
+
}
|
|
18861
|
+
}
|
|
18862
|
+
// ── GitHub API helpers ──────────────────────────────────────────────
|
|
18863
|
+
async githubFetch(token, owner, repo, method, pathSegment, body) {
|
|
18864
|
+
const url = `https://api.github.com/repos/${owner}/${repo}${pathSegment}`;
|
|
18865
|
+
const res = await fetch(url, {
|
|
18866
|
+
method,
|
|
18867
|
+
headers: {
|
|
18868
|
+
Authorization: `Bearer ${token}`,
|
|
18869
|
+
Accept: "application/vnd.github+json",
|
|
18870
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
18871
|
+
"Content-Type": "application/json"
|
|
18872
|
+
},
|
|
18873
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
18874
|
+
});
|
|
18875
|
+
if (!res.ok) {
|
|
18876
|
+
const errText = await res.text();
|
|
18877
|
+
throw new Error(`GitHub API ${method} ${pathSegment} failed (${res.status}): ${errText}`);
|
|
18878
|
+
}
|
|
18879
|
+
const text = await res.text();
|
|
18880
|
+
return text ? JSON.parse(text) : {};
|
|
18881
|
+
}
|
|
18882
|
+
async getRef(token, owner, repo, ref) {
|
|
18883
|
+
return await this.githubFetch(token, owner, repo, "GET", `/git/refs/heads/${ref}`);
|
|
18884
|
+
}
|
|
18885
|
+
async updateRef(token, owner, repo, ref, sha) {
|
|
18886
|
+
await this.githubFetch(token, owner, repo, "PATCH", `/git/refs/heads/${ref}`, {
|
|
18887
|
+
sha,
|
|
18888
|
+
force: false
|
|
18889
|
+
});
|
|
18890
|
+
}
|
|
18891
|
+
async getCommit(token, owner, repo, sha) {
|
|
18892
|
+
return await this.githubFetch(token, owner, repo, "GET", `/git/commits/${sha}`);
|
|
18893
|
+
}
|
|
18894
|
+
async getTreeEntries(token, owner, repo, treeSha) {
|
|
18895
|
+
return await this.githubFetch(token, owner, repo, "GET", `/git/trees/${treeSha}?recursive=1`);
|
|
18896
|
+
}
|
|
18897
|
+
async createCommit(token, owner, repo, treeSha, parentSha, message = "sync") {
|
|
18898
|
+
const body = { message, tree: treeSha };
|
|
18899
|
+
if (parentSha) body.parents = [parentSha];
|
|
18900
|
+
const result = await this.githubFetch(token, owner, repo, "POST", "/git/commits", body);
|
|
18901
|
+
return result.sha;
|
|
18902
|
+
}
|
|
18903
|
+
async createGitTree(token, owner, repo, entries, baseTreeSha) {
|
|
18904
|
+
const tree = entries.map((e) => ({
|
|
18905
|
+
path: e.path,
|
|
18906
|
+
mode: e.mode,
|
|
18907
|
+
type: "blob",
|
|
18908
|
+
content: e.content
|
|
18909
|
+
}));
|
|
18910
|
+
const body = { tree };
|
|
18911
|
+
if (baseTreeSha) body.base_tree = baseTreeSha;
|
|
18912
|
+
const result = await this.githubFetch(token, owner, repo, "POST", "/git/trees", body);
|
|
18913
|
+
return result.sha;
|
|
18914
|
+
}
|
|
18915
|
+
async getBlob(token, owner, repo, sha) {
|
|
18916
|
+
const result = await this.githubFetch(token, owner, repo, "GET", `/git/blobs/${sha}`);
|
|
18917
|
+
return result.content;
|
|
18918
|
+
}
|
|
18919
|
+
// ── Local file helpers ──────────────────────────────────────────────
|
|
18920
|
+
async buildLocalTree(categories) {
|
|
18921
|
+
const entries = [];
|
|
18922
|
+
const hashes = [];
|
|
18923
|
+
for (const cat of categories) {
|
|
18924
|
+
const localPath = this.categoryToPath(cat);
|
|
18925
|
+
if (!localPath) continue;
|
|
18926
|
+
try {
|
|
18927
|
+
const stat9 = await fsp2.stat(localPath);
|
|
18928
|
+
if (stat9.isDirectory()) {
|
|
18929
|
+
const files = await this.walkDir(localPath, localPath);
|
|
18930
|
+
for (const file of files) {
|
|
18931
|
+
const content = await fsp2.readFile(file, "utf8");
|
|
18932
|
+
const rel = path6.relative(localPath, file).replace(/\\/g, "/");
|
|
18933
|
+
entries.push({ path: `data/${cat}/${rel}`, content, mode: "100644" });
|
|
18934
|
+
hashes.push(content);
|
|
18935
|
+
}
|
|
18936
|
+
} else {
|
|
18937
|
+
const content = await fsp2.readFile(localPath, "utf8");
|
|
18938
|
+
entries.push({ path: `data/${cat}`, content, mode: "100644" });
|
|
18939
|
+
hashes.push(content);
|
|
18940
|
+
}
|
|
18941
|
+
} catch {
|
|
18942
|
+
}
|
|
18943
|
+
}
|
|
18944
|
+
const rev = createHash("sha256").update(hashes.join("")).digest("hex").slice(0, 12);
|
|
18945
|
+
return { treeEntries: entries, rev };
|
|
18946
|
+
}
|
|
18947
|
+
async hashLocalCategories(categories) {
|
|
18948
|
+
const hashes = [];
|
|
18949
|
+
for (const cat of categories) {
|
|
18950
|
+
const localPath = this.categoryToPath(cat);
|
|
18951
|
+
if (!localPath) continue;
|
|
18952
|
+
try {
|
|
18953
|
+
const stat9 = await fsp2.stat(localPath);
|
|
18954
|
+
if (stat9.isDirectory()) {
|
|
18955
|
+
const files = await this.walkDir(localPath, localPath);
|
|
18956
|
+
for (const file of files) {
|
|
18957
|
+
const content = await fsp2.readFile(file);
|
|
18958
|
+
hashes.push(content.toString("base64") + file);
|
|
18959
|
+
}
|
|
18960
|
+
} else {
|
|
18961
|
+
const content = await fsp2.readFile(localPath);
|
|
18962
|
+
hashes.push(content.toString("base64") + localPath);
|
|
18963
|
+
}
|
|
18964
|
+
} catch {
|
|
18965
|
+
}
|
|
18966
|
+
}
|
|
18967
|
+
return createHash("sha256").update(hashes.join("")).digest("hex").slice(0, 12);
|
|
18968
|
+
}
|
|
18969
|
+
categoryToPath(cat) {
|
|
18970
|
+
switch (cat) {
|
|
18971
|
+
case "settings":
|
|
18972
|
+
return this.paths.globalConfig;
|
|
18973
|
+
case "skills":
|
|
18974
|
+
return this.paths.globalSkills;
|
|
18975
|
+
case "prompts":
|
|
18976
|
+
return this.paths.globalPrompts;
|
|
18977
|
+
case "memory":
|
|
18978
|
+
return this.paths.globalMemory;
|
|
18979
|
+
case "history":
|
|
18980
|
+
return this.paths.historyFile;
|
|
18981
|
+
default:
|
|
18982
|
+
return null;
|
|
18983
|
+
}
|
|
18984
|
+
}
|
|
18985
|
+
async walkDir(dir, base) {
|
|
18986
|
+
const results = [];
|
|
18987
|
+
const entries = await fsp2.readdir(dir, { withFileTypes: true });
|
|
18988
|
+
for (const entry of entries) {
|
|
18989
|
+
const full = path6.join(dir, entry.name);
|
|
18990
|
+
if (entry.isDirectory()) {
|
|
18991
|
+
results.push(...await this.walkDir(full, base));
|
|
18992
|
+
} else {
|
|
18993
|
+
results.push(full);
|
|
18994
|
+
}
|
|
18995
|
+
}
|
|
18996
|
+
return results;
|
|
18997
|
+
}
|
|
18998
|
+
};
|
|
18999
|
+
function timeAgo(iso) {
|
|
19000
|
+
const diff = Date.now() - new Date(iso).getTime();
|
|
19001
|
+
const mins = Math.floor(diff / 6e4);
|
|
19002
|
+
if (mins < 1) return "just now";
|
|
19003
|
+
if (mins < 60) return `${mins}m ago`;
|
|
19004
|
+
const hrs = Math.floor(mins / 60);
|
|
19005
|
+
if (hrs < 24) return `${hrs}h ago`;
|
|
19006
|
+
const days = Math.floor(hrs / 24);
|
|
19007
|
+
return `${days}d ago`;
|
|
19008
|
+
}
|
|
18627
19009
|
var MATCHERS = {
|
|
18628
19010
|
pnpmWorkspace: (files) => files.includes("pnpm-workspace.yaml"),
|
|
18629
19011
|
gradlew: (_files, dirs) => dirs.includes("gradlew"),
|
|
@@ -19501,8 +19883,8 @@ var ReportGenerator = class {
|
|
|
19501
19883
|
try {
|
|
19502
19884
|
await stat(this.options.outputDir);
|
|
19503
19885
|
} catch {
|
|
19504
|
-
const { mkdir:
|
|
19505
|
-
await
|
|
19886
|
+
const { mkdir: mkdir15 } = await import('fs/promises');
|
|
19887
|
+
await mkdir15(this.options.outputDir, { recursive: true });
|
|
19506
19888
|
}
|
|
19507
19889
|
}
|
|
19508
19890
|
generateMarkdown(result) {
|
|
@@ -20424,16 +20806,16 @@ Use \`/security report <number>\` to view a specific report.` };
|
|
|
20424
20806
|
}
|
|
20425
20807
|
const index = Number.parseInt(reportId, 10) - 1;
|
|
20426
20808
|
if (!Number.isNaN(index) && reports[index]) {
|
|
20427
|
-
const { readFile:
|
|
20428
|
-
const content = await
|
|
20809
|
+
const { readFile: readFile34 } = await import('fs/promises');
|
|
20810
|
+
const content = await readFile34(join(reportsDir, reports[index]), "utf-8");
|
|
20429
20811
|
return { message: `# Security Report
|
|
20430
20812
|
|
|
20431
20813
|
${content}` };
|
|
20432
20814
|
}
|
|
20433
20815
|
const match = reports.find((r) => r.includes(reportId));
|
|
20434
20816
|
if (match) {
|
|
20435
|
-
const { readFile:
|
|
20436
|
-
const content = await
|
|
20817
|
+
const { readFile: readFile34 } = await import('fs/promises');
|
|
20818
|
+
const content = await readFile34(join(reportsDir, match), "utf-8");
|
|
20437
20819
|
return { message: `# Security Report
|
|
20438
20820
|
|
|
20439
20821
|
${content}` };
|
|
@@ -20793,7 +21175,7 @@ function createMcpControlTool(opts) {
|
|
|
20793
21175
|
description: "Manage MCP server lifecycle: list available servers, search by name or capability, enable or disable servers at runtime, restart running servers.",
|
|
20794
21176
|
category: "mcp",
|
|
20795
21177
|
permission: "auto",
|
|
20796
|
-
mutating:
|
|
21178
|
+
mutating: true,
|
|
20797
21179
|
riskTier: "standard",
|
|
20798
21180
|
inputSchema,
|
|
20799
21181
|
async execute(raw, ctx) {
|
|
@@ -22595,8 +22977,8 @@ ${mem}`);
|
|
|
22595
22977
|
}
|
|
22596
22978
|
async dirExists(p) {
|
|
22597
22979
|
try {
|
|
22598
|
-
const
|
|
22599
|
-
return
|
|
22980
|
+
const stat9 = await fsp2.stat(p);
|
|
22981
|
+
return stat9.isDirectory();
|
|
22600
22982
|
} catch {
|
|
22601
22983
|
return false;
|
|
22602
22984
|
}
|
|
@@ -22857,39 +23239,55 @@ var ProviderRegistry = class {
|
|
|
22857
23239
|
var SlashCommandRegistry = class {
|
|
22858
23240
|
cmds = /* @__PURE__ */ new Map();
|
|
22859
23241
|
/**
|
|
22860
|
-
* Register a command.
|
|
22861
|
-
*
|
|
22862
|
-
*
|
|
23242
|
+
* Register a command.
|
|
23243
|
+
*
|
|
23244
|
+
* Trust tiers, by `owner` and `opts.official`:
|
|
23245
|
+
*
|
|
23246
|
+
* - **Built-ins** (`owner === 'core'`) and **official plugins**
|
|
23247
|
+
* (`opts.official === true`, set by the host only for first-party plugins
|
|
23248
|
+
* loaded from the built-in factory list) claim the **bare** command name
|
|
23249
|
+
* (`/prompts`). They may override one another — last write wins — so an
|
|
23250
|
+
* official plugin can replace a built-in. Official plugins are *also*
|
|
23251
|
+
* reachable under their `owner:name` namespace.
|
|
23252
|
+
* - **External plugins** (any other `owner`) are isolated under the
|
|
23253
|
+
* `owner:name` namespace: invocable only as `/owner:cmd`, never by bare
|
|
23254
|
+
* name, and unable to shadow or override a built-in or official command.
|
|
23255
|
+
*
|
|
23256
|
+
* Officiality is supplied by the host based on the plugin's load source, not
|
|
23257
|
+
* self-declared by the plugin — an external plugin cannot name itself into
|
|
23258
|
+
* the official tier.
|
|
22863
23259
|
*/
|
|
22864
|
-
register(cmd, owner = "core") {
|
|
23260
|
+
register(cmd, owner = "core", opts) {
|
|
22865
23261
|
const isPlugin = owner !== "core";
|
|
22866
|
-
const
|
|
22867
|
-
|
|
22868
|
-
|
|
22869
|
-
|
|
23262
|
+
const official = !isPlugin || opts?.official === true;
|
|
23263
|
+
if (official) {
|
|
23264
|
+
const existing = this.cmds.get(cmd.name);
|
|
23265
|
+
if (existing) {
|
|
23266
|
+
const existingIsOfficialPlugin = existing.official && existing.owner !== "core";
|
|
23267
|
+
if (!isPlugin && existingIsOfficialPlugin) {
|
|
23268
|
+
return;
|
|
23269
|
+
}
|
|
23270
|
+
if (!isPlugin && existing.owner === "core") return;
|
|
22870
23271
|
}
|
|
22871
|
-
|
|
22872
|
-
if (this.cmds.has(fullName)) {
|
|
22873
|
-
if (!isPlugin) return;
|
|
22874
|
-
this.cmds.set(fullName, { cmd, owner });
|
|
23272
|
+
this.cmds.set(cmd.name, { cmd, owner, official });
|
|
22875
23273
|
for (const a of cmd.aliases ?? []) {
|
|
22876
|
-
this.cmds.set(
|
|
23274
|
+
this.cmds.set(a, { cmd, owner, official });
|
|
22877
23275
|
}
|
|
22878
|
-
return;
|
|
22879
23276
|
}
|
|
22880
|
-
|
|
22881
|
-
|
|
22882
|
-
|
|
23277
|
+
if (isPlugin) {
|
|
23278
|
+
this.cmds.set(`${owner}:${cmd.name}`, { cmd, owner, official });
|
|
23279
|
+
for (const a of cmd.aliases ?? []) {
|
|
23280
|
+
this.cmds.set(`${owner}:${a}`, { cmd, owner, official });
|
|
23281
|
+
}
|
|
22883
23282
|
}
|
|
22884
23283
|
}
|
|
22885
23284
|
unregister(name) {
|
|
22886
23285
|
const entry = this.cmds.get(name);
|
|
22887
23286
|
if (!entry) return false;
|
|
22888
|
-
for (const
|
|
22889
|
-
|
|
22890
|
-
this.cmds.delete(fullAlias);
|
|
23287
|
+
for (const [key, e] of this.cmds.entries()) {
|
|
23288
|
+
if (e.cmd === entry.cmd) this.cmds.delete(key);
|
|
22891
23289
|
}
|
|
22892
|
-
return
|
|
23290
|
+
return true;
|
|
22893
23291
|
}
|
|
22894
23292
|
/**
|
|
22895
23293
|
* Bulk-register multiple slash commands at once.
|
|
@@ -23008,8 +23406,9 @@ var DefaultPluginAPI = class {
|
|
|
23008
23406
|
};
|
|
23009
23407
|
this.mcp = init.mcpRegistry ?? noopMcp;
|
|
23010
23408
|
const scr = init.slashCommandRegistry;
|
|
23409
|
+
const official = init.official === true;
|
|
23011
23410
|
this.slashCommands = scr ? {
|
|
23012
|
-
register: (cmd) => scr.register(cmd, owner),
|
|
23411
|
+
register: (cmd) => scr.register(cmd, owner, { official }),
|
|
23013
23412
|
unregister: (name) => scr.unregister(name),
|
|
23014
23413
|
get: (name) => scr.get(name),
|
|
23015
23414
|
list: () => scr.list()
|
|
@@ -24897,6 +25296,1054 @@ function assertSafePath(dir, projectRoot) {
|
|
|
24897
25296
|
}
|
|
24898
25297
|
}
|
|
24899
25298
|
|
|
24900
|
-
|
|
25299
|
+
// src/plugins/prompts-plugin.ts
|
|
25300
|
+
function createPromptsPlugin(opts) {
|
|
25301
|
+
let store = null;
|
|
25302
|
+
return {
|
|
25303
|
+
name: "wstack-prompts",
|
|
25304
|
+
version: "1.0.0",
|
|
25305
|
+
description: "Personal prompt library with LLM-powered enhancements",
|
|
25306
|
+
apiVersion: "^0.1",
|
|
25307
|
+
capabilities: { slashCommands: true },
|
|
25308
|
+
defaultConfig: {},
|
|
25309
|
+
setup(api) {
|
|
25310
|
+
const rawConfig = api.config;
|
|
25311
|
+
const paths = opts?.paths ?? rawConfig.paths;
|
|
25312
|
+
store = opts?.store ?? (paths ? new DefaultPromptStore(paths) : null);
|
|
25313
|
+
api.slashCommands.register(buildPromptsCommand(store));
|
|
25314
|
+
api.log.info("[prompts] loaded \u2014 /prompts available");
|
|
25315
|
+
},
|
|
25316
|
+
teardown(api) {
|
|
25317
|
+
api.slashCommands.unregister("prompts");
|
|
25318
|
+
api.log.info("[prompts] unloaded");
|
|
25319
|
+
},
|
|
25320
|
+
async health() {
|
|
25321
|
+
return { ok: true, message: "Prompt store accessible" };
|
|
25322
|
+
}
|
|
25323
|
+
};
|
|
25324
|
+
}
|
|
25325
|
+
function buildPromptsCommand(store) {
|
|
25326
|
+
return {
|
|
25327
|
+
name: "prompts",
|
|
25328
|
+
description: "Manage your prompt library: /prompts [list|view|add|delete|edit|extend]",
|
|
25329
|
+
async run(args, ctx) {
|
|
25330
|
+
if (!store) return { message: "Prompt store not available." };
|
|
25331
|
+
const [verb, ...rest] = args.trim().split(/\s+/);
|
|
25332
|
+
const restJoined = rest.join(" ");
|
|
25333
|
+
switch (verb) {
|
|
25334
|
+
case "":
|
|
25335
|
+
case "list":
|
|
25336
|
+
case "ls": {
|
|
25337
|
+
const entries = await store.list();
|
|
25338
|
+
if (entries.length === 0) {
|
|
25339
|
+
return { message: 'Prompt library empty. Add: /prompts add "title" "content"' };
|
|
25340
|
+
}
|
|
25341
|
+
const lines = entries.map(
|
|
25342
|
+
(e) => ` ${e.title} ${dim2(e.id)} ${e.tags.join(", ") || ""}`
|
|
25343
|
+
);
|
|
25344
|
+
return { message: `Prompt library (${entries.length}):
|
|
25345
|
+
${lines.join("\n")}
|
|
25346
|
+
` };
|
|
25347
|
+
}
|
|
25348
|
+
case "view":
|
|
25349
|
+
case "show": {
|
|
25350
|
+
if (!restJoined) return { message: "Usage: /prompts view <title>" };
|
|
25351
|
+
const matches = await store.find(restJoined);
|
|
25352
|
+
if (matches.length === 0) return { message: `No prompt matching "${restJoined}".` };
|
|
25353
|
+
const entry = matches.find((m) => m.title.toLowerCase() === restJoined.toLowerCase()) ?? matches[0];
|
|
25354
|
+
const tags = entry.tags.length > 0 ? ` [${entry.tags.join(", ")}]` : "";
|
|
25355
|
+
return {
|
|
25356
|
+
message: `# ${entry.title}${tags}
|
|
25357
|
+
|
|
25358
|
+
${entry.content}
|
|
25359
|
+
|
|
25360
|
+
${dim2(`id: ${entry.id} | created: ${entry.createdAt}`)}`
|
|
25361
|
+
};
|
|
25362
|
+
}
|
|
25363
|
+
case "add":
|
|
25364
|
+
case "new": {
|
|
25365
|
+
const parsed = parseTitleContent(restJoined);
|
|
25366
|
+
if (!parsed.title) return { message: 'Usage: /prompts add "title" "prompt content"' };
|
|
25367
|
+
const entry = store.createNew(parsed.title, parsed.content);
|
|
25368
|
+
await store.save(entry);
|
|
25369
|
+
return { message: `Added prompt "${entry.title}" (${entry.id}).` };
|
|
25370
|
+
}
|
|
25371
|
+
case "delete":
|
|
25372
|
+
case "rm": {
|
|
25373
|
+
if (!restJoined) return { message: "Usage: /prompts delete <title>" };
|
|
25374
|
+
const matches = await store.find(restJoined);
|
|
25375
|
+
if (matches.length === 0) return { message: `No prompt matching "${restJoined}".` };
|
|
25376
|
+
const exact = matches.find((m) => m.title.toLowerCase() === restJoined.toLowerCase()) ?? matches[0];
|
|
25377
|
+
const deleted = await store.delete(exact.id);
|
|
25378
|
+
return { message: deleted ? `Deleted "${exact.title}".` : "Delete failed." };
|
|
25379
|
+
}
|
|
25380
|
+
case "edit":
|
|
25381
|
+
case "update": {
|
|
25382
|
+
const parsed = parseTitleContent(restJoined);
|
|
25383
|
+
if (!parsed.title) return { message: 'Usage: /prompts edit "title" "new content"' };
|
|
25384
|
+
const matches = await store.find(parsed.title);
|
|
25385
|
+
if (matches.length === 0) return { message: `No prompt matching "${parsed.title}".` };
|
|
25386
|
+
const exact = matches.find((m) => m.title.toLowerCase() === parsed.title.toLowerCase()) ?? matches[0];
|
|
25387
|
+
exact.content = parsed.content;
|
|
25388
|
+
exact.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25389
|
+
await store.save(exact);
|
|
25390
|
+
return { message: `Updated "${exact.title}".` };
|
|
25391
|
+
}
|
|
25392
|
+
case "extend": {
|
|
25393
|
+
if (!restJoined) return { message: 'Usage: /prompts extend "title" <instructions>' };
|
|
25394
|
+
const parsed = parseTitleContent(restJoined);
|
|
25395
|
+
if (!parsed.title) return { message: 'Usage: /prompts extend "title" <instructions>' };
|
|
25396
|
+
const matches = await store.find(parsed.title);
|
|
25397
|
+
if (matches.length === 0) return { message: `No prompt matching "${parsed.title}".` };
|
|
25398
|
+
const exact = matches.find((m) => m.title.toLowerCase() === parsed.title.toLowerCase()) ?? matches[0];
|
|
25399
|
+
const prov = ctx.provider;
|
|
25400
|
+
if (!prov?.complete) return { message: "LLM not available. Configure a provider first." };
|
|
25401
|
+
const enhanced = await prov.complete(ctx.model, [
|
|
25402
|
+
"[SYSTEM INSTRUCTIONS]",
|
|
25403
|
+
"You are a prompt engineering assistant. Improve the following prompt by integrating the additional instructions seamlessly. Keep the same tone and format. Return only the improved prompt.",
|
|
25404
|
+
"",
|
|
25405
|
+
`EXISTING PROMPT:
|
|
25406
|
+
${exact.content}`,
|
|
25407
|
+
"",
|
|
25408
|
+
`ADDITIONAL INSTRUCTIONS:
|
|
25409
|
+
${parsed.content}`,
|
|
25410
|
+
"",
|
|
25411
|
+
"Respond with ONLY the improved prompt, no commentary."
|
|
25412
|
+
].join("\n"));
|
|
25413
|
+
exact.content = enhanced.trim();
|
|
25414
|
+
exact.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25415
|
+
await store.save(exact);
|
|
25416
|
+
return { message: `Extended "${exact.title}".
|
|
25417
|
+
|
|
25418
|
+
${dim2("New content:")}
|
|
25419
|
+
${exact.content}` };
|
|
25420
|
+
}
|
|
25421
|
+
default:
|
|
25422
|
+
return {
|
|
25423
|
+
message: `Unknown subcommand "${verb}". Try: list | view | add | delete | edit | extend`
|
|
25424
|
+
};
|
|
25425
|
+
}
|
|
25426
|
+
}
|
|
25427
|
+
};
|
|
25428
|
+
}
|
|
25429
|
+
function parseTitleContent(args) {
|
|
25430
|
+
const trimmed = args.trim();
|
|
25431
|
+
if (!trimmed) return { title: "", content: "" };
|
|
25432
|
+
const doubleMatch = /^"([^"]+)"\s+"([^"]+)"$/.exec(trimmed) || /^'([^']+)'\s+'([^']+)'$/.exec(trimmed);
|
|
25433
|
+
if (doubleMatch) return { title: doubleMatch[1], content: doubleMatch[2] };
|
|
25434
|
+
const singleMatch = /^'([^']+)'\s+(.+)$/.exec(trimmed);
|
|
25435
|
+
if (singleMatch) return { title: singleMatch[1], content: singleMatch[2] };
|
|
25436
|
+
const firstSpace = trimmed.indexOf(" ");
|
|
25437
|
+
if (firstSpace === -1) return { title: trimmed, content: "" };
|
|
25438
|
+
return { title: trimmed.slice(0, firstSpace), content: trimmed.slice(firstSpace + 1) };
|
|
25439
|
+
}
|
|
25440
|
+
function dim2(s) {
|
|
25441
|
+
return `\x1B[2m${s}\x1B[0m`;
|
|
25442
|
+
}
|
|
25443
|
+
|
|
25444
|
+
// src/plugins/sync-plugin.ts
|
|
25445
|
+
function createSyncPlugin(opts) {
|
|
25446
|
+
let cloud = null;
|
|
25447
|
+
let configStore;
|
|
25448
|
+
return {
|
|
25449
|
+
name: "wstack-sync",
|
|
25450
|
+
version: "1.0.0",
|
|
25451
|
+
description: "GitHub cloud sync for prompts, skills, settings, memory and history",
|
|
25452
|
+
apiVersion: "^0.1",
|
|
25453
|
+
capabilities: { slashCommands: true },
|
|
25454
|
+
defaultConfig: {},
|
|
25455
|
+
setup(api) {
|
|
25456
|
+
const rawConfig = api.config;
|
|
25457
|
+
const paths = opts?.paths ?? rawConfig.paths;
|
|
25458
|
+
configStore = opts?.configStore ?? rawConfig.configStore;
|
|
25459
|
+
if (!paths || !configStore) {
|
|
25460
|
+
api.log.warn("[sync] paths or configStore not available \u2014 /sync disabled");
|
|
25461
|
+
return;
|
|
25462
|
+
}
|
|
25463
|
+
cloud = new CloudSync(
|
|
25464
|
+
paths,
|
|
25465
|
+
() => {
|
|
25466
|
+
const cfg = configStore.get();
|
|
25467
|
+
return cfg.sync;
|
|
25468
|
+
},
|
|
25469
|
+
async (cfg) => {
|
|
25470
|
+
configStore.update({ sync: cfg });
|
|
25471
|
+
}
|
|
25472
|
+
);
|
|
25473
|
+
void cloud.loadState();
|
|
25474
|
+
api.slashCommands.register(buildSyncCommand(cloud, configStore));
|
|
25475
|
+
api.log.info("[sync] loaded \u2014 /sync available. Run /sync to get started.");
|
|
25476
|
+
},
|
|
25477
|
+
teardown(api) {
|
|
25478
|
+
api.slashCommands.unregister("sync");
|
|
25479
|
+
api.log.info("[sync] unloaded");
|
|
25480
|
+
},
|
|
25481
|
+
async health() {
|
|
25482
|
+
return { ok: true, message: "CloudSync ready" };
|
|
25483
|
+
}
|
|
25484
|
+
};
|
|
25485
|
+
}
|
|
25486
|
+
function buildSyncCommand(cloud, configStore) {
|
|
25487
|
+
return {
|
|
25488
|
+
name: "sync",
|
|
25489
|
+
description: "Cloud sync: /sync [status|enable|disable|push|pull|categories]",
|
|
25490
|
+
async run(args, _ctx) {
|
|
25491
|
+
const [verb, ...rest] = args.trim().split(/\s+/);
|
|
25492
|
+
const restJoined = rest.join(" ");
|
|
25493
|
+
switch (verb) {
|
|
25494
|
+
case "":
|
|
25495
|
+
case "status": {
|
|
25496
|
+
const msg = await cloud.status();
|
|
25497
|
+
return { message: msg };
|
|
25498
|
+
}
|
|
25499
|
+
case "enable": {
|
|
25500
|
+
const parts = restJoined.trim().split(/\s+/);
|
|
25501
|
+
if (parts.length < 2) {
|
|
25502
|
+
return {
|
|
25503
|
+
message: [
|
|
25504
|
+
"Usage: /sync enable owner/repo TOKEN [cat1 cat2 ...]",
|
|
25505
|
+
" e.g. /sync enable myname/wrongstack-data ghp_xxx",
|
|
25506
|
+
` Categories (default: all): ${ALL_SYNC_CATEGORIES.join(", ")}`
|
|
25507
|
+
].join("\n")
|
|
25508
|
+
};
|
|
25509
|
+
}
|
|
25510
|
+
const [repo, token, ...cats] = parts;
|
|
25511
|
+
if (!repo || !repo.includes("/")) {
|
|
25512
|
+
return { message: 'Invalid repo format. Expected "owner/repo".' };
|
|
25513
|
+
}
|
|
25514
|
+
const syncConfig = {
|
|
25515
|
+
enabled: true,
|
|
25516
|
+
repo,
|
|
25517
|
+
githubToken: token,
|
|
25518
|
+
categories: cats.length > 0 ? cats : ALL_SYNC_CATEGORIES,
|
|
25519
|
+
lastSyncedAt: void 0
|
|
25520
|
+
};
|
|
25521
|
+
configStore.update({ sync: syncConfig });
|
|
25522
|
+
await cloud.loadState();
|
|
25523
|
+
return {
|
|
25524
|
+
message: [
|
|
25525
|
+
`CloudSync enabled for ${repo}`,
|
|
25526
|
+
` categories: ${syncConfig.categories.join(", ")}`,
|
|
25527
|
+
"",
|
|
25528
|
+
"Run `/sync push` to upload your data to GitHub."
|
|
25529
|
+
].join("\n")
|
|
25530
|
+
};
|
|
25531
|
+
}
|
|
25532
|
+
case "disable": {
|
|
25533
|
+
const msg = await cloud.disable();
|
|
25534
|
+
return { message: msg };
|
|
25535
|
+
}
|
|
25536
|
+
case "push": {
|
|
25537
|
+
const cfg = configStore.get().sync;
|
|
25538
|
+
if (!cfg?.enabled) return { message: "CloudSync not enabled. Run `/sync enable`." };
|
|
25539
|
+
if (!cfg?.githubToken) return { message: "No GitHub token found. Run `/sync enable owner/repo TOKEN`." };
|
|
25540
|
+
let result;
|
|
25541
|
+
try {
|
|
25542
|
+
result = await cloud.push(cfg.githubToken);
|
|
25543
|
+
} catch (err) {
|
|
25544
|
+
return { message: `Push failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
25545
|
+
}
|
|
25546
|
+
if (result.ok) {
|
|
25547
|
+
configStore.update({
|
|
25548
|
+
sync: { ...cfg, lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
25549
|
+
});
|
|
25550
|
+
}
|
|
25551
|
+
return { message: result.message };
|
|
25552
|
+
}
|
|
25553
|
+
case "pull": {
|
|
25554
|
+
const cfg = configStore.get().sync;
|
|
25555
|
+
if (!cfg?.enabled) return { message: "CloudSync not enabled. Run `/sync enable`." };
|
|
25556
|
+
if (!cfg?.githubToken) return { message: "No GitHub token. Run `/sync enable owner/repo TOKEN`." };
|
|
25557
|
+
let result;
|
|
25558
|
+
try {
|
|
25559
|
+
result = await cloud.pull(cfg.githubToken);
|
|
25560
|
+
} catch (err) {
|
|
25561
|
+
return { message: `Pull failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
25562
|
+
}
|
|
25563
|
+
if (result.ok) {
|
|
25564
|
+
configStore.update({
|
|
25565
|
+
sync: { ...cfg, lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
25566
|
+
});
|
|
25567
|
+
}
|
|
25568
|
+
return { message: result.message };
|
|
25569
|
+
}
|
|
25570
|
+
case "categories": {
|
|
25571
|
+
const [action, ...catRest] = restJoined.trim().split(/\s+/);
|
|
25572
|
+
const cfg = configStore.get().sync;
|
|
25573
|
+
if (!cfg?.enabled) return { message: "CloudSync not enabled. Run `/sync enable`." };
|
|
25574
|
+
if (action === "list" || !action) {
|
|
25575
|
+
return {
|
|
25576
|
+
message: [
|
|
25577
|
+
`Synced categories: ${cfg.categories.join(", ")}`,
|
|
25578
|
+
`Available: ${ALL_SYNC_CATEGORIES.join(", ")}`,
|
|
25579
|
+
"Update: /sync categories add <name> | remove <name>"
|
|
25580
|
+
].join("\n")
|
|
25581
|
+
};
|
|
25582
|
+
}
|
|
25583
|
+
if (action === "add") {
|
|
25584
|
+
const catName = catRest[0];
|
|
25585
|
+
if (!ALL_SYNC_CATEGORIES.includes(catName)) {
|
|
25586
|
+
return { message: `Unknown. Available: ${ALL_SYNC_CATEGORIES.join(", ")}` };
|
|
25587
|
+
}
|
|
25588
|
+
if (cfg.categories.includes(catName)) return { message: `"${catName}" already synced.` };
|
|
25589
|
+
configStore.update({
|
|
25590
|
+
sync: { ...cfg, categories: [...cfg.categories, catName] }
|
|
25591
|
+
});
|
|
25592
|
+
return { message: `Added "${catName}" to sync categories.` };
|
|
25593
|
+
}
|
|
25594
|
+
if (action === "remove") {
|
|
25595
|
+
const catName = catRest[0];
|
|
25596
|
+
if (!cfg.categories.includes(catName)) return { message: `"${catName}" not in sync categories.` };
|
|
25597
|
+
configStore.update({
|
|
25598
|
+
sync: { ...cfg, categories: cfg.categories.filter((c) => c !== catName) }
|
|
25599
|
+
});
|
|
25600
|
+
return { message: `Removed "${catName}" from sync categories.` };
|
|
25601
|
+
}
|
|
25602
|
+
return { message: "Usage: /sync categories list | add <name> | remove <name>" };
|
|
25603
|
+
}
|
|
25604
|
+
default:
|
|
25605
|
+
return {
|
|
25606
|
+
message: [
|
|
25607
|
+
"/sync \u2014 Cloud Sync",
|
|
25608
|
+
"",
|
|
25609
|
+
" /sync status Show current sync status",
|
|
25610
|
+
" /sync enable owner/repo TOKEN Enable for a repo (TOKEN = fine-grained PAT)",
|
|
25611
|
+
" /sync disable Disable sync (keeps local data)",
|
|
25612
|
+
" /sync push Upload selected categories",
|
|
25613
|
+
" /sync pull Download from repo",
|
|
25614
|
+
" /sync categories list|add|remove Manage synced categories",
|
|
25615
|
+
"",
|
|
25616
|
+
`Categories: ${ALL_SYNC_CATEGORIES.join(", ")}`
|
|
25617
|
+
].join("\n")
|
|
25618
|
+
};
|
|
25619
|
+
}
|
|
25620
|
+
}
|
|
25621
|
+
};
|
|
25622
|
+
}
|
|
25623
|
+
function createGitPlugin() {
|
|
25624
|
+
return {
|
|
25625
|
+
name: "wstack-git",
|
|
25626
|
+
version: "1.0.0",
|
|
25627
|
+
description: "Git helpers: /commit (LLM message), /gitcheck, /push",
|
|
25628
|
+
apiVersion: "^0.1",
|
|
25629
|
+
capabilities: { slashCommands: true },
|
|
25630
|
+
defaultConfig: {},
|
|
25631
|
+
setup(api) {
|
|
25632
|
+
api.slashCommands.register(buildCommitCommand());
|
|
25633
|
+
api.slashCommands.register(buildGitcheckCommand());
|
|
25634
|
+
api.slashCommands.register(buildPushCommand());
|
|
25635
|
+
api.log.info("[git] loaded \u2014 /commit, /gitcheck, /push available");
|
|
25636
|
+
},
|
|
25637
|
+
teardown(api) {
|
|
25638
|
+
api.slashCommands.unregister("commit");
|
|
25639
|
+
api.slashCommands.unregister("gitcheck");
|
|
25640
|
+
api.slashCommands.unregister("push");
|
|
25641
|
+
api.log.info("[git] unloaded");
|
|
25642
|
+
},
|
|
25643
|
+
async health() {
|
|
25644
|
+
return { ok: true, message: "git helpers ready" };
|
|
25645
|
+
}
|
|
25646
|
+
};
|
|
25647
|
+
}
|
|
25648
|
+
async function runGit(args, cwd) {
|
|
25649
|
+
try {
|
|
25650
|
+
return await new Promise((resolve9, reject) => {
|
|
25651
|
+
const child = spawn("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
25652
|
+
let stdout = "";
|
|
25653
|
+
let stderr = "";
|
|
25654
|
+
child.stdout?.on("data", (d) => {
|
|
25655
|
+
stdout += d;
|
|
25656
|
+
});
|
|
25657
|
+
child.stderr?.on("data", (d) => {
|
|
25658
|
+
stderr += d;
|
|
25659
|
+
});
|
|
25660
|
+
child.on("error", (err) => {
|
|
25661
|
+
reject(
|
|
25662
|
+
new WrongStackError({
|
|
25663
|
+
message: `Failed to run git: ${err.message}`,
|
|
25664
|
+
code: ERROR_CODES.TOOL_EXECUTION_FAILED,
|
|
25665
|
+
subsystem: "tool",
|
|
25666
|
+
context: { command: "git", args, cwd },
|
|
25667
|
+
cause: err
|
|
25668
|
+
})
|
|
25669
|
+
);
|
|
25670
|
+
});
|
|
25671
|
+
child.on("close", (code) => resolve9({ stdout, stderr, code: code ?? 0 }));
|
|
25672
|
+
});
|
|
25673
|
+
} catch (err) {
|
|
25674
|
+
if (err instanceof WrongStackError) throw err;
|
|
25675
|
+
throw new WrongStackError({
|
|
25676
|
+
message: err instanceof Error ? err.message : String(err),
|
|
25677
|
+
code: ERROR_CODES.TOOL_EXECUTION_FAILED,
|
|
25678
|
+
subsystem: "tool",
|
|
25679
|
+
context: { command: "git", args, cwd },
|
|
25680
|
+
cause: err
|
|
25681
|
+
});
|
|
25682
|
+
}
|
|
25683
|
+
}
|
|
25684
|
+
async function isGitRepo(cwd) {
|
|
25685
|
+
const result = await runGit(["rev-parse", "--git-dir"], cwd);
|
|
25686
|
+
return result.code === 0;
|
|
25687
|
+
}
|
|
25688
|
+
async function hasUncommittedChanges(cwd) {
|
|
25689
|
+
const result = await runGit(["status", "--porcelain"], cwd);
|
|
25690
|
+
return result.stdout.trim().length > 0;
|
|
25691
|
+
}
|
|
25692
|
+
function asLLMProvider(provider) {
|
|
25693
|
+
if (provider && typeof provider.complete === "function") {
|
|
25694
|
+
return provider;
|
|
25695
|
+
}
|
|
25696
|
+
return null;
|
|
25697
|
+
}
|
|
25698
|
+
async function generateCommitMessageWithLLM(diff, provider, model) {
|
|
25699
|
+
const systemPrompt = "You are a helpful assistant that generates concise, conventional-commit-formatted git commit messages. Analyze the provided diff and output ONLY the commit message (no explanation, no quotes). Format: <type>(<scope>): <short description> \u2014 <type> is one of: feat, fix, docs, style, refactor, test, chore, perf, ci, build, temp. If the diff contains multiple unrelated changes, pick the most important one. Keep the description under 72 characters. Example: feat(cli): add /commit LLM integration";
|
|
25700
|
+
try {
|
|
25701
|
+
const ac = new AbortController();
|
|
25702
|
+
const timeout = setTimeout(() => ac.abort(), 15e3);
|
|
25703
|
+
const resp = await provider.complete(
|
|
25704
|
+
{
|
|
25705
|
+
model,
|
|
25706
|
+
system: [{ type: "text", text: systemPrompt }],
|
|
25707
|
+
messages: [
|
|
25708
|
+
{ role: "user", content: [{ type: "text", text: `Here is the git diff:
|
|
25709
|
+
|
|
25710
|
+
${diff}` }] }
|
|
25711
|
+
],
|
|
25712
|
+
maxTokens: 80,
|
|
25713
|
+
temperature: 0.3
|
|
25714
|
+
},
|
|
25715
|
+
{ signal: ac.signal }
|
|
25716
|
+
);
|
|
25717
|
+
clearTimeout(timeout);
|
|
25718
|
+
const raw = resp.content;
|
|
25719
|
+
const text = Array.isArray(raw) ? raw[0]?.text ?? "" : typeof raw === "object" && raw !== null ? raw.text ?? "" : String(raw ?? "");
|
|
25720
|
+
const message = text.trim().split("\n")[0] ?? "";
|
|
25721
|
+
if (message.length > 0 && message.length < 200) return message;
|
|
25722
|
+
} catch {
|
|
25723
|
+
}
|
|
25724
|
+
return null;
|
|
25725
|
+
}
|
|
25726
|
+
function detectCommitType(stats) {
|
|
25727
|
+
const lines = stats.split("\n");
|
|
25728
|
+
const hasTestFiles = lines.some(
|
|
25729
|
+
(l) => l.includes("_test.") || l.includes(".test.") || l.includes(".spec.")
|
|
25730
|
+
);
|
|
25731
|
+
const hasDocs = lines.some(
|
|
25732
|
+
(l) => l.includes("README") || l.includes("CHANGELOG") || l.includes("docs/") || l.includes(".md")
|
|
25733
|
+
);
|
|
25734
|
+
const hasConfig = lines.some(
|
|
25735
|
+
(l) => l.includes("config") || l.includes("tsconfig") || l.includes(".json")
|
|
25736
|
+
);
|
|
25737
|
+
if (hasTestFiles) return "test";
|
|
25738
|
+
if (hasDocs) return "docs";
|
|
25739
|
+
if (hasConfig) return "chore";
|
|
25740
|
+
return "feat";
|
|
25741
|
+
}
|
|
25742
|
+
async function generateCommitMessageHeuristics(cwd) {
|
|
25743
|
+
const statsResult = await runGit(["diff", "--stat"], cwd);
|
|
25744
|
+
if (statsResult.code !== 0) return "chore: update";
|
|
25745
|
+
const nameResult = await runGit(["diff", "--name-only"], cwd);
|
|
25746
|
+
const files = nameResult.stdout.split("\n").filter(Boolean);
|
|
25747
|
+
const commitType = detectCommitType(statsResult.stdout);
|
|
25748
|
+
let scope = "";
|
|
25749
|
+
if (files.length > 0) {
|
|
25750
|
+
const primary = files[0].split("/")[0];
|
|
25751
|
+
if (primary && primary !== "packages" && primary !== "apps" && primary !== "node_modules") {
|
|
25752
|
+
scope = `(${primary})`;
|
|
25753
|
+
}
|
|
25754
|
+
}
|
|
25755
|
+
if (files.length === 0) return `${commitType}${scope}: update`;
|
|
25756
|
+
if (files.length <= 3) {
|
|
25757
|
+
const summary2 = files.map((f) => f.split("/").pop()).join(", ");
|
|
25758
|
+
return `${commitType}${scope}: ${summary2}`;
|
|
25759
|
+
}
|
|
25760
|
+
const summary = `${files.slice(0, 3).map((f) => f.split("/").pop()).join(", ")} and ${files.length - 3} more`;
|
|
25761
|
+
return `${commitType}${scope}: ${summary}`;
|
|
25762
|
+
}
|
|
25763
|
+
function buildCommitCommand() {
|
|
25764
|
+
return {
|
|
25765
|
+
name: "commit",
|
|
25766
|
+
description: "Stage all changes and commit with auto-generated message.",
|
|
25767
|
+
aliases: ["gc"],
|
|
25768
|
+
async run(args, ctx) {
|
|
25769
|
+
const cwd = ctx?.cwd ?? process.cwd();
|
|
25770
|
+
if (!await isGitRepo(cwd)) return { message: "Not a git repository." };
|
|
25771
|
+
if (!await hasUncommittedChanges(cwd)) {
|
|
25772
|
+
return { message: "Nothing to commit (working tree clean)." };
|
|
25773
|
+
}
|
|
25774
|
+
const dryRun = args.includes("--dry-run") || args.includes("-n");
|
|
25775
|
+
const noLlm = args.includes("--no-llm");
|
|
25776
|
+
let message = null;
|
|
25777
|
+
const provider = noLlm ? null : asLLMProvider(ctx?.provider);
|
|
25778
|
+
if (provider && ctx?.model) {
|
|
25779
|
+
const diff = (await runGit(["diff"], cwd)).stdout;
|
|
25780
|
+
message = await generateCommitMessageWithLLM(diff, provider, ctx.model);
|
|
25781
|
+
}
|
|
25782
|
+
if (!message) message = await generateCommitMessageHeuristics(cwd);
|
|
25783
|
+
if (dryRun) {
|
|
25784
|
+
return {
|
|
25785
|
+
message: `Would commit:
|
|
25786
|
+
|
|
25787
|
+
${color.green(message)}
|
|
25788
|
+
|
|
25789
|
+
${color.dim("(dry-run \u2014 no actual commit)")}`
|
|
25790
|
+
};
|
|
25791
|
+
}
|
|
25792
|
+
const stageResult = await runGit(["add", "."], cwd);
|
|
25793
|
+
if (stageResult.code !== 0) return { message: `Stage failed: ${stageResult.stderr}` };
|
|
25794
|
+
const commitResult = await runGit(["commit", "-m", message], cwd);
|
|
25795
|
+
if (commitResult.code !== 0) return { message: `Commit failed: ${commitResult.stderr}` };
|
|
25796
|
+
const hash = (await runGit(["rev-parse", "--short", "HEAD"], cwd)).stdout.trim();
|
|
25797
|
+
const hasRemote = (await runGit(["remote"], cwd)).stdout.trim().length > 0;
|
|
25798
|
+
const pushMsg = hasRemote ? `
|
|
25799
|
+
|
|
25800
|
+
${color.dim("Tip: Run /push to push to remote")}` : "";
|
|
25801
|
+
return {
|
|
25802
|
+
message: `${color.green("\u2713")} Committed: ${color.bold(message)}
|
|
25803
|
+
${color.dim(hash)}${pushMsg}`
|
|
25804
|
+
};
|
|
25805
|
+
}
|
|
25806
|
+
};
|
|
25807
|
+
}
|
|
25808
|
+
function buildGitcheckCommand() {
|
|
25809
|
+
return {
|
|
25810
|
+
name: "gitcheck",
|
|
25811
|
+
description: "Check for uncommitted changes (for system prompt integration).",
|
|
25812
|
+
aliases: ["gcstatus"],
|
|
25813
|
+
async run(_args, ctx) {
|
|
25814
|
+
const cwd = ctx?.cwd ?? process.cwd();
|
|
25815
|
+
if (!await isGitRepo(cwd)) return { message: "" };
|
|
25816
|
+
if (!await hasUncommittedChanges(cwd)) return { message: "" };
|
|
25817
|
+
const statusResult = await runGit(["status", "--porcelain"], cwd);
|
|
25818
|
+
const count = statusResult.stdout.split("\n").filter(Boolean).length;
|
|
25819
|
+
if (count === 0) return { message: "" };
|
|
25820
|
+
return {
|
|
25821
|
+
message: `\u26A0 ${color.yellow(`${count} uncommitted change${count > 1 ? "s" : ""}`)} \u2014 consider /commit`
|
|
25822
|
+
};
|
|
25823
|
+
}
|
|
25824
|
+
};
|
|
25825
|
+
}
|
|
25826
|
+
function buildPushCommand() {
|
|
25827
|
+
return {
|
|
25828
|
+
name: "push",
|
|
25829
|
+
description: "Push to remote after commit.",
|
|
25830
|
+
async run(args, ctx) {
|
|
25831
|
+
const cwd = ctx?.cwd ?? process.cwd();
|
|
25832
|
+
if (!await isGitRepo(cwd)) return { message: "Not a git repository." };
|
|
25833
|
+
const dryRun = args.includes("--dry-run") || args.includes("-n");
|
|
25834
|
+
const force = args.includes("--force") || args.includes("-f");
|
|
25835
|
+
const remotes = (await runGit(["remote"], cwd)).stdout.split("\n").filter(Boolean);
|
|
25836
|
+
if (remotes.length === 0) {
|
|
25837
|
+
return { message: "No remote configured. Add one with: git remote add origin <url>" };
|
|
25838
|
+
}
|
|
25839
|
+
if (dryRun) {
|
|
25840
|
+
return {
|
|
25841
|
+
message: `Would push to ${remotes.join(", ")}${force ? " (force)" : ""}
|
|
25842
|
+
${color.dim("(dry-run)")}`
|
|
25843
|
+
};
|
|
25844
|
+
}
|
|
25845
|
+
const branch = (await runGit(["rev-parse", "--abbrev-ref", "HEAD"], cwd)).stdout.trim() || "main";
|
|
25846
|
+
const pushArgs = ["push"];
|
|
25847
|
+
if (force) pushArgs.push("--force");
|
|
25848
|
+
pushArgs.push(...remotes, branch);
|
|
25849
|
+
const pushResult = await runGit(pushArgs, cwd);
|
|
25850
|
+
if (pushResult.code !== 0) return { message: `Push failed: ${pushResult.stderr}` };
|
|
25851
|
+
return { message: `${color.green("\u2713")} Pushed to ${remotes.join(", ")} (${branch})` };
|
|
25852
|
+
}
|
|
25853
|
+
};
|
|
25854
|
+
}
|
|
25855
|
+
|
|
25856
|
+
// src/plugins/observability-plugin.ts
|
|
25857
|
+
function createObservabilityPlugin(opts) {
|
|
25858
|
+
return {
|
|
25859
|
+
name: "wstack-observability",
|
|
25860
|
+
version: "1.0.0",
|
|
25861
|
+
description: "Runtime metrics and health checks: /metrics, /health",
|
|
25862
|
+
apiVersion: "^0.1",
|
|
25863
|
+
capabilities: { slashCommands: true },
|
|
25864
|
+
defaultConfig: {},
|
|
25865
|
+
setup(api) {
|
|
25866
|
+
const rawConfig = api.config;
|
|
25867
|
+
const metricsSink = opts?.metricsSink ?? rawConfig.metricsSink;
|
|
25868
|
+
const healthRegistry = opts?.healthRegistry ?? rawConfig.healthRegistry;
|
|
25869
|
+
api.slashCommands.register(buildMetricsCommand(metricsSink));
|
|
25870
|
+
api.slashCommands.register(buildHealthCommand(healthRegistry));
|
|
25871
|
+
api.log.info("[observability] loaded \u2014 /metrics, /health available");
|
|
25872
|
+
},
|
|
25873
|
+
teardown(api) {
|
|
25874
|
+
api.slashCommands.unregister("metrics");
|
|
25875
|
+
api.slashCommands.unregister("health");
|
|
25876
|
+
api.log.info("[observability] unloaded");
|
|
25877
|
+
},
|
|
25878
|
+
async health() {
|
|
25879
|
+
return { ok: true, message: "observability ready" };
|
|
25880
|
+
}
|
|
25881
|
+
};
|
|
25882
|
+
}
|
|
25883
|
+
function statusIcon(status) {
|
|
25884
|
+
if (status === "healthy") return color.green("\u25CF");
|
|
25885
|
+
if (status === "degraded") return color.yellow("\u25CF");
|
|
25886
|
+
return color.red("\u25CF");
|
|
25887
|
+
}
|
|
25888
|
+
function buildMetricsCommand(metricsSink) {
|
|
25889
|
+
return {
|
|
25890
|
+
name: "metrics",
|
|
25891
|
+
description: "Show metrics snapshot (requires --metrics flag).",
|
|
25892
|
+
async run() {
|
|
25893
|
+
if (!metricsSink) {
|
|
25894
|
+
return { message: "Metrics not enabled. Restart with --metrics to collect." };
|
|
25895
|
+
}
|
|
25896
|
+
const snap = metricsSink.snapshot();
|
|
25897
|
+
if (snap.series.length === 0) return { message: "No metrics recorded yet." };
|
|
25898
|
+
const lines = [];
|
|
25899
|
+
const byName = /* @__PURE__ */ new Map();
|
|
25900
|
+
for (const s of snap.series) {
|
|
25901
|
+
const bucket = byName.get(s.name) ?? [];
|
|
25902
|
+
bucket.push(s);
|
|
25903
|
+
byName.set(s.name, bucket);
|
|
25904
|
+
}
|
|
25905
|
+
for (const [name, series] of [...byName.entries()].sort()) {
|
|
25906
|
+
lines.push(color.dim(`# ${name}`));
|
|
25907
|
+
for (const s of series) {
|
|
25908
|
+
const labels = Object.entries(s.labels).map(([k, v]) => `${k}=${v}`).join(" ");
|
|
25909
|
+
const labelStr = labels ? color.dim(` {${labels}}`) : "";
|
|
25910
|
+
if (s.type === "histogram") {
|
|
25911
|
+
lines.push(
|
|
25912
|
+
` count=${s.values.count} sum=${s.values.sum} min=${s.values.min} max=${s.values.max} p50=${s.values.p50} p95=${s.values.p95} p99=${s.values.p99}${labelStr}`
|
|
25913
|
+
);
|
|
25914
|
+
} else {
|
|
25915
|
+
lines.push(` ${s.values.value}${labelStr}`);
|
|
25916
|
+
}
|
|
25917
|
+
}
|
|
25918
|
+
}
|
|
25919
|
+
return { message: lines.join("\n") };
|
|
25920
|
+
}
|
|
25921
|
+
};
|
|
25922
|
+
}
|
|
25923
|
+
function buildHealthCommand(healthRegistry) {
|
|
25924
|
+
return {
|
|
25925
|
+
name: "health",
|
|
25926
|
+
description: "Run health checks (requires --metrics flag).",
|
|
25927
|
+
async run() {
|
|
25928
|
+
if (!healthRegistry) {
|
|
25929
|
+
return { message: "Health checks not enabled. Restart with --metrics." };
|
|
25930
|
+
}
|
|
25931
|
+
const result = await healthRegistry.run();
|
|
25932
|
+
const lines = [
|
|
25933
|
+
`${statusIcon(result.status)} overall: ${result.status}`,
|
|
25934
|
+
...result.checks.map((c) => {
|
|
25935
|
+
const detail = c.detail ? color.dim(` \u2014 ${c.detail}`) : "";
|
|
25936
|
+
return ` ${statusIcon(c.status)} ${c.name}: ${c.status}${detail}`;
|
|
25937
|
+
})
|
|
25938
|
+
];
|
|
25939
|
+
return { message: lines.join("\n") };
|
|
25940
|
+
}
|
|
25941
|
+
};
|
|
25942
|
+
}
|
|
25943
|
+
|
|
25944
|
+
// src/plugins/security-plugin.ts
|
|
25945
|
+
function createSecurityPlugin() {
|
|
25946
|
+
return {
|
|
25947
|
+
name: "wstack-security",
|
|
25948
|
+
version: "1.0.0",
|
|
25949
|
+
description: "Security scanning: /security scan | audit | report",
|
|
25950
|
+
apiVersion: "^0.1",
|
|
25951
|
+
capabilities: { slashCommands: true },
|
|
25952
|
+
defaultConfig: {},
|
|
25953
|
+
setup(api) {
|
|
25954
|
+
api.slashCommands.register(createSecuritySlashCommand());
|
|
25955
|
+
api.log.info("[security] loaded \u2014 /security available");
|
|
25956
|
+
},
|
|
25957
|
+
teardown(api) {
|
|
25958
|
+
api.slashCommands.unregister("security");
|
|
25959
|
+
api.log.info("[security] unloaded");
|
|
25960
|
+
},
|
|
25961
|
+
async health() {
|
|
25962
|
+
return { ok: true, message: "security scanner ready" };
|
|
25963
|
+
}
|
|
25964
|
+
};
|
|
25965
|
+
}
|
|
25966
|
+
function createSkillsPlugin(opts) {
|
|
25967
|
+
return {
|
|
25968
|
+
name: "wstack-skills",
|
|
25969
|
+
version: "1.0.0",
|
|
25970
|
+
description: "Skill library and GitHub installer: /skill, /skill-gen, /skill-install, ...",
|
|
25971
|
+
apiVersion: "^0.1",
|
|
25972
|
+
capabilities: { slashCommands: true },
|
|
25973
|
+
defaultConfig: {},
|
|
25974
|
+
setup(api) {
|
|
25975
|
+
const rawConfig = api.config;
|
|
25976
|
+
const skillLoader = opts?.skillLoader ?? rawConfig.skillLoader;
|
|
25977
|
+
api.slashCommands.register(buildSkillCommand(skillLoader));
|
|
25978
|
+
api.slashCommands.register(buildSkillGeneratorCommand(skillLoader));
|
|
25979
|
+
api.slashCommands.register(buildSkillInstallCommand(skillLoader));
|
|
25980
|
+
api.slashCommands.register(buildSkillUpdateCommand(skillLoader));
|
|
25981
|
+
api.slashCommands.register(buildSkillUninstallCommand(skillLoader));
|
|
25982
|
+
api.log.info("[skills] loaded \u2014 /skill, /skill-gen, /skill-install/update/uninstall available");
|
|
25983
|
+
},
|
|
25984
|
+
teardown(api) {
|
|
25985
|
+
for (const name of ["skill", "skill-gen", "skill-install", "skill-update", "skill-uninstall"]) {
|
|
25986
|
+
api.slashCommands.unregister(name);
|
|
25987
|
+
}
|
|
25988
|
+
api.log.info("[skills] unloaded");
|
|
25989
|
+
},
|
|
25990
|
+
async health() {
|
|
25991
|
+
return { ok: true, message: "skills ready" };
|
|
25992
|
+
}
|
|
25993
|
+
};
|
|
25994
|
+
}
|
|
25995
|
+
function makeInstaller(skillLoader, projectRoot) {
|
|
25996
|
+
const globalRoot = path6.join(os5.homedir(), ".wrongstack");
|
|
25997
|
+
return new SkillInstaller({
|
|
25998
|
+
manifestPath: path6.join(globalRoot, "installed-skills.json"),
|
|
25999
|
+
projectSkillsDir: path6.join(projectRoot, ".wrongstack", "skills"),
|
|
26000
|
+
globalSkillsDir: path6.join(globalRoot, "skills"),
|
|
26001
|
+
projectHash: projectHash(projectRoot),
|
|
26002
|
+
skillLoader
|
|
26003
|
+
});
|
|
26004
|
+
}
|
|
26005
|
+
function buildSkillCommand(skillLoader) {
|
|
26006
|
+
return {
|
|
26007
|
+
name: "skill",
|
|
26008
|
+
description: "Show skill details or list available skills. Use /skill-gen to create new skills.",
|
|
26009
|
+
async run(args) {
|
|
26010
|
+
if (!skillLoader) return { message: "No skill loader configured." };
|
|
26011
|
+
if (!args.trim()) {
|
|
26012
|
+
const entries = await skillLoader.listEntries();
|
|
26013
|
+
if (entries.length === 0) return { message: "No skills found." };
|
|
26014
|
+
const lines = entries.map((e) => {
|
|
26015
|
+
const scopeTag = e.scope.length > 0 ? ` ${color.dim(`(${e.scope.slice(0, 3).join(", ")})`)}` : "";
|
|
26016
|
+
return ` ${color.bold(e.name)}${scopeTag}
|
|
26017
|
+
Use when: ${e.trigger}`;
|
|
26018
|
+
});
|
|
26019
|
+
return { message: `Available skills:
|
|
26020
|
+
${lines.join("\n\n")}
|
|
26021
|
+
` };
|
|
26022
|
+
}
|
|
26023
|
+
const skill = await skillLoader.find(args.trim());
|
|
26024
|
+
if (!skill) return { message: `Skill "${args.trim()}" not found.` };
|
|
26025
|
+
return { message: await skillLoader.readBody(skill.name) };
|
|
26026
|
+
}
|
|
26027
|
+
};
|
|
26028
|
+
}
|
|
26029
|
+
function buildSkillGeneratorCommand(skillLoader) {
|
|
26030
|
+
return {
|
|
26031
|
+
name: "skill-gen",
|
|
26032
|
+
description: "Create a new AI skill interactively. The AI will guide you.",
|
|
26033
|
+
help: [
|
|
26034
|
+
"\u2554\u2550\u2550\u2550 Skill Generator \u2550\u2550\u2550\u2557",
|
|
26035
|
+
"",
|
|
26036
|
+
"Create new AI skills with AI guidance.",
|
|
26037
|
+
"",
|
|
26038
|
+
"Usage:",
|
|
26039
|
+
" /skill-gen Start skill creation",
|
|
26040
|
+
" /skill-gen list List existing skills",
|
|
26041
|
+
" /skill-gen edit <name> View an existing skill",
|
|
26042
|
+
"",
|
|
26043
|
+
"The AI will ask you questions and create the skill file.",
|
|
26044
|
+
"Skills are saved to .wrongstack/skills/<name>/SKILL.md"
|
|
26045
|
+
].join("\n"),
|
|
26046
|
+
async run(args) {
|
|
26047
|
+
const trimmed = args.trim();
|
|
26048
|
+
if (trimmed === "list" || trimmed === "ls") {
|
|
26049
|
+
if (!skillLoader) return { message: "No skill loader configured." };
|
|
26050
|
+
const entries = await skillLoader.listEntries();
|
|
26051
|
+
if (entries.length === 0) return { message: "No skills found." };
|
|
26052
|
+
const lines = entries.map((e) => {
|
|
26053
|
+
const src = e.source === "project" ? "\u{1F4C1}" : e.source === "user" ? "\u{1F464}" : "\u{1F4E6}";
|
|
26054
|
+
return ` ${src} ${e.name}
|
|
26055
|
+
${e.trigger}`;
|
|
26056
|
+
});
|
|
26057
|
+
return { message: `Available Skills:
|
|
26058
|
+
${lines.join("\n\n")}
|
|
26059
|
+
` };
|
|
26060
|
+
}
|
|
26061
|
+
if (trimmed.startsWith("edit ")) {
|
|
26062
|
+
const skillName = trimmed.slice(5).trim();
|
|
26063
|
+
if (!skillLoader) return { message: "No skill loader configured." };
|
|
26064
|
+
const skill = await skillLoader.find(skillName);
|
|
26065
|
+
if (!skill) return { message: `Skill "${skillName}" not found.` };
|
|
26066
|
+
const body = await skillLoader.readBody(skillName);
|
|
26067
|
+
return { message: [`Skill: ${skillName}`, `Path: ${skill.path}`, "", body].join("\n") };
|
|
26068
|
+
}
|
|
26069
|
+
return {
|
|
26070
|
+
message: "\u2554\u2550\u2550\u2550 Skill Generator \u2550\u2550\u2550\u2557\n\nThe AI will guide you through creating a new skill.\nAnswer its questions naturally.",
|
|
26071
|
+
runText: "I want to create a new AI skill. Read the skill-creator skill and guide me through the process. Ask me questions one at a time \u2014 name, description, what to cover \u2014 then create the SKILL.md file."
|
|
26072
|
+
};
|
|
26073
|
+
}
|
|
26074
|
+
};
|
|
26075
|
+
}
|
|
26076
|
+
function buildSkillInstallCommand(skillLoader) {
|
|
26077
|
+
return {
|
|
26078
|
+
name: "skill-install",
|
|
26079
|
+
description: "Install skills from a GitHub repository.",
|
|
26080
|
+
argsHint: "<user/repo[@ref]> [--global]",
|
|
26081
|
+
help: [
|
|
26082
|
+
"\u2554\u2550\u2550\u2550 Skill Install \u2550\u2550\u2550\u2557",
|
|
26083
|
+
"",
|
|
26084
|
+
"Install skills from a GitHub repository.",
|
|
26085
|
+
"",
|
|
26086
|
+
"Usage:",
|
|
26087
|
+
" /skill-install <user/repo> Install from default branch (main)",
|
|
26088
|
+
" /skill-install <user/repo@ref> Install specific tag/branch/commit",
|
|
26089
|
+
" /skill-install <user/repo> --global Install to user-global skills",
|
|
26090
|
+
"",
|
|
26091
|
+
"Supports both single-skill repos (SKILL.md at root)",
|
|
26092
|
+
"and multi-skill repos (skills/ subdirectory).",
|
|
26093
|
+
"",
|
|
26094
|
+
"Examples:",
|
|
26095
|
+
" /skill-install wrongstack/awesome-skills",
|
|
26096
|
+
" /skill-install wrongstack/skills@v1.0",
|
|
26097
|
+
" /skill-install user/my-skills --global"
|
|
26098
|
+
].join("\n"),
|
|
26099
|
+
async run(args, ctx) {
|
|
26100
|
+
const parts = args.trim().split(/\s+/);
|
|
26101
|
+
const ref = parts.find((p) => !p.startsWith("--"));
|
|
26102
|
+
const isGlobal = parts.includes("--global");
|
|
26103
|
+
if (!ref) return { message: "Usage: /skill-install <user/repo[@ref]> [--global]" };
|
|
26104
|
+
const installer = makeInstaller(skillLoader, ctx.projectRoot);
|
|
26105
|
+
try {
|
|
26106
|
+
const results = await installer.install(ref, { global: isGlobal });
|
|
26107
|
+
if (results.length === 0) return { message: "No skills found in the repository." };
|
|
26108
|
+
const scope = isGlobal ? "user-global" : "project";
|
|
26109
|
+
const lines = [`Installed ${results.length} skill(s) [${scope}]:`];
|
|
26110
|
+
for (const r of results) {
|
|
26111
|
+
lines.push(` \u2713 ${r.name} (${r.source}@${r.ref})`);
|
|
26112
|
+
lines.push(` \u2192 ${r.path}`);
|
|
26113
|
+
}
|
|
26114
|
+
return { message: lines.join("\n") };
|
|
26115
|
+
} catch (err) {
|
|
26116
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
26117
|
+
return { message: `\u2717 Install failed: ${msg}` };
|
|
26118
|
+
}
|
|
26119
|
+
}
|
|
26120
|
+
};
|
|
26121
|
+
}
|
|
26122
|
+
function buildSkillUpdateCommand(skillLoader) {
|
|
26123
|
+
return {
|
|
26124
|
+
name: "skill-update",
|
|
26125
|
+
description: "Update installed skills from their GitHub source.",
|
|
26126
|
+
argsHint: "[name|ref] [--global]",
|
|
26127
|
+
help: [
|
|
26128
|
+
"\u2554\u2550\u2550\u2550 Skill Update \u2550\u2550\u2550\u2557",
|
|
26129
|
+
"",
|
|
26130
|
+
"Update installed skills from their GitHub source.",
|
|
26131
|
+
"",
|
|
26132
|
+
"Usage:",
|
|
26133
|
+
" /skill-update Update all installed skills",
|
|
26134
|
+
" /skill-update <name> Update a specific skill",
|
|
26135
|
+
" /skill-update <user/repo@ref> Update to a different ref",
|
|
26136
|
+
" /skill-update <name> --global Update a global skill"
|
|
26137
|
+
].join("\n"),
|
|
26138
|
+
async run(args, ctx) {
|
|
26139
|
+
const parts = args.trim().split(/\s+/);
|
|
26140
|
+
const nameOrRef = parts.find((p) => !p.startsWith("--"));
|
|
26141
|
+
const isGlobal = parts.includes("--global");
|
|
26142
|
+
const installer = makeInstaller(skillLoader, ctx.projectRoot);
|
|
26143
|
+
try {
|
|
26144
|
+
const result = await installer.update(nameOrRef, { global: isGlobal });
|
|
26145
|
+
const lines = [];
|
|
26146
|
+
if (result.updated.length > 0) {
|
|
26147
|
+
lines.push(`Updated ${result.updated.length} skill(s):`);
|
|
26148
|
+
for (const u of result.updated) {
|
|
26149
|
+
lines.push(
|
|
26150
|
+
u.oldRef !== u.newRef ? ` \u2713 ${u.name} (${u.oldRef} \u2192 ${u.newRef})` : ` \u2713 ${u.name} (refreshed)`
|
|
26151
|
+
);
|
|
26152
|
+
}
|
|
26153
|
+
}
|
|
26154
|
+
if (result.unchanged.length > 0) lines.push(`Up to date: ${result.unchanged.join(", ")}`);
|
|
26155
|
+
if (result.errors.length > 0) {
|
|
26156
|
+
for (const e of result.errors) lines.push(` \u2717 ${e.name}: ${e.error}`);
|
|
26157
|
+
}
|
|
26158
|
+
if (lines.length === 0) return { message: "No installed skills to update." };
|
|
26159
|
+
return { message: lines.join("\n") };
|
|
26160
|
+
} catch (err) {
|
|
26161
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
26162
|
+
return { message: `\u2717 Update failed: ${msg}` };
|
|
26163
|
+
}
|
|
26164
|
+
}
|
|
26165
|
+
};
|
|
26166
|
+
}
|
|
26167
|
+
function buildSkillUninstallCommand(skillLoader) {
|
|
26168
|
+
return {
|
|
26169
|
+
name: "skill-uninstall",
|
|
26170
|
+
description: "Remove an installed skill.",
|
|
26171
|
+
argsHint: "<name> [--global]",
|
|
26172
|
+
help: [
|
|
26173
|
+
"\u2554\u2550\u2550\u2550 Skill Uninstall \u2550\u2550\u2550\u2557",
|
|
26174
|
+
"",
|
|
26175
|
+
"Remove an installed skill and its files.",
|
|
26176
|
+
"",
|
|
26177
|
+
"Usage:",
|
|
26178
|
+
" /skill-uninstall <name> Remove from project skills",
|
|
26179
|
+
" /skill-uninstall <name> --global Remove from user-global skills"
|
|
26180
|
+
].join("\n"),
|
|
26181
|
+
async run(args, ctx) {
|
|
26182
|
+
const parts = args.trim().split(/\s+/);
|
|
26183
|
+
const name = parts.find((p) => !p.startsWith("--"));
|
|
26184
|
+
const isGlobal = parts.includes("--global");
|
|
26185
|
+
if (!name) {
|
|
26186
|
+
const installer2 = makeInstaller(skillLoader, ctx.projectRoot);
|
|
26187
|
+
const installed = await installer2.listInstalled();
|
|
26188
|
+
if (installed.length === 0) return { message: "No installed skills found." };
|
|
26189
|
+
const scope = isGlobal ? "user" : "project";
|
|
26190
|
+
const filtered = installed.filter((s) => s.scope === scope);
|
|
26191
|
+
if (filtered.length === 0) {
|
|
26192
|
+
return { message: `No installed skills found (${scope} scope).` };
|
|
26193
|
+
}
|
|
26194
|
+
const lines = [`Installed skills (${scope}):`];
|
|
26195
|
+
for (const s of filtered) {
|
|
26196
|
+
lines.push(` ${s.name} ${s.source}@${s.ref} (${s.installedAt.slice(0, 10)})`);
|
|
26197
|
+
}
|
|
26198
|
+
lines.push("", "Use /skill-uninstall <name> to remove.");
|
|
26199
|
+
return { message: lines.join("\n") };
|
|
26200
|
+
}
|
|
26201
|
+
const installer = makeInstaller(skillLoader, ctx.projectRoot);
|
|
26202
|
+
try {
|
|
26203
|
+
await installer.uninstall(name, { global: isGlobal });
|
|
26204
|
+
return { message: `\u2713 Skill "${name}" uninstalled.` };
|
|
26205
|
+
} catch (err) {
|
|
26206
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
26207
|
+
return { message: `\u2717 Uninstall failed: ${msg}` };
|
|
26208
|
+
}
|
|
26209
|
+
}
|
|
26210
|
+
};
|
|
26211
|
+
}
|
|
26212
|
+
|
|
26213
|
+
// src/plugins/plan-plugin.ts
|
|
26214
|
+
function createPlanPlugin(opts) {
|
|
26215
|
+
return {
|
|
26216
|
+
name: "wstack-plan",
|
|
26217
|
+
version: "1.0.0",
|
|
26218
|
+
description: "Strategic plan board: /plan show | add | start | done | promote | template | clear",
|
|
26219
|
+
apiVersion: "^0.1",
|
|
26220
|
+
capabilities: { slashCommands: true },
|
|
26221
|
+
defaultConfig: {},
|
|
26222
|
+
setup(api) {
|
|
26223
|
+
const rawConfig = api.config;
|
|
26224
|
+
const paths = opts?.paths ?? rawConfig.paths;
|
|
26225
|
+
api.slashCommands.register(buildPlanCommand(paths?.projectPlan));
|
|
26226
|
+
api.log.info("[plan] loaded \u2014 /plan available");
|
|
26227
|
+
},
|
|
26228
|
+
teardown(api) {
|
|
26229
|
+
api.slashCommands.unregister("plan");
|
|
26230
|
+
api.log.info("[plan] unloaded");
|
|
26231
|
+
},
|
|
26232
|
+
async health() {
|
|
26233
|
+
return { ok: true, message: "plan board ready" };
|
|
26234
|
+
}
|
|
26235
|
+
};
|
|
26236
|
+
}
|
|
26237
|
+
function buildPlanCommand(planPath) {
|
|
26238
|
+
return {
|
|
26239
|
+
name: "plan",
|
|
26240
|
+
description: "Strategic plan board: /plan [show|add <title>|start <id|#>|done <id|#>|remove <id|#>|promote <id|#> [subtask ...]|derive <id|#>|template [list|use <name>]|clear]",
|
|
26241
|
+
async run(args, ctx) {
|
|
26242
|
+
if (!planPath) return { message: "Plan storage is not configured for this session." };
|
|
26243
|
+
const sessionId = ctx?.session?.id ?? "unknown";
|
|
26244
|
+
const [verb, ...rest] = args.trim().split(/\s+/);
|
|
26245
|
+
const restJoined = rest.join(" ").trim();
|
|
26246
|
+
const plan = await loadPlan(planPath) ?? emptyPlan(sessionId);
|
|
26247
|
+
switch (verb) {
|
|
26248
|
+
case "":
|
|
26249
|
+
case "show":
|
|
26250
|
+
case "list":
|
|
26251
|
+
return { message: formatPlan(plan) };
|
|
26252
|
+
case "add": {
|
|
26253
|
+
if (!restJoined) return { message: "Usage: /plan add <title>" };
|
|
26254
|
+
const { plan: updated, item } = addPlanItem(plan, restJoined);
|
|
26255
|
+
await savePlan(planPath, updated);
|
|
26256
|
+
return { message: `Added: ${item.title}
|
|
26257
|
+
${formatPlan(updated)}` };
|
|
26258
|
+
}
|
|
26259
|
+
case "start":
|
|
26260
|
+
case "progress": {
|
|
26261
|
+
if (!restJoined) return { message: "Usage: /plan start <id|index>" };
|
|
26262
|
+
const updated = setPlanItemStatus(plan, restJoined, "in_progress");
|
|
26263
|
+
await savePlan(planPath, updated);
|
|
26264
|
+
return { message: formatPlan(updated) };
|
|
26265
|
+
}
|
|
26266
|
+
case "done":
|
|
26267
|
+
case "complete": {
|
|
26268
|
+
if (!restJoined) return { message: "Usage: /plan done <id|index>" };
|
|
26269
|
+
const updated = setPlanItemStatus(plan, restJoined, "done");
|
|
26270
|
+
await savePlan(planPath, updated);
|
|
26271
|
+
return { message: formatPlan(updated) };
|
|
26272
|
+
}
|
|
26273
|
+
case "remove":
|
|
26274
|
+
case "delete":
|
|
26275
|
+
case "rm": {
|
|
26276
|
+
if (!restJoined) return { message: "Usage: /plan remove <id|index>" };
|
|
26277
|
+
const updated = removePlanItem(plan, restJoined);
|
|
26278
|
+
await savePlan(planPath, updated);
|
|
26279
|
+
return { message: formatPlan(updated) };
|
|
26280
|
+
}
|
|
26281
|
+
case "promote": {
|
|
26282
|
+
if (!restJoined) return { message: "Usage: /plan promote <id|index> [subtask ...]" };
|
|
26283
|
+
const [target, ...subtasks] = restJoined.split(/\s+/);
|
|
26284
|
+
if (!target) return { message: "Usage: /plan promote <id|index> [subtask ...]" };
|
|
26285
|
+
const derived = deriveTodosFromPlanItem(plan, target, subtasks.length > 0 ? subtasks : void 0);
|
|
26286
|
+
if (!derived) return { message: `No plan item matched "${target}".` };
|
|
26287
|
+
await savePlan(planPath, derived.plan);
|
|
26288
|
+
ctx?.state?.replaceTodos(derived.todos);
|
|
26289
|
+
return {
|
|
26290
|
+
message: `Promoted to ${derived.todos.length} todo(s):
|
|
26291
|
+
${formatTodosList(derived.todos)}
|
|
26292
|
+
|
|
26293
|
+
${formatPlan(derived.plan)}`
|
|
26294
|
+
};
|
|
26295
|
+
}
|
|
26296
|
+
case "derive": {
|
|
26297
|
+
if (!restJoined) return { message: "Usage: /plan derive <id|index>" };
|
|
26298
|
+
const derived = deriveTodosFromPlanItem(plan, restJoined);
|
|
26299
|
+
if (!derived) return { message: `No plan item matched "${restJoined}".` };
|
|
26300
|
+
await savePlan(planPath, derived.plan);
|
|
26301
|
+
ctx?.state?.replaceTodos(derived.todos);
|
|
26302
|
+
return {
|
|
26303
|
+
message: `Derived ${derived.todos.length} todo(s):
|
|
26304
|
+
${formatTodosList(derived.todos)}
|
|
26305
|
+
|
|
26306
|
+
${formatPlan(derived.plan)}`
|
|
26307
|
+
};
|
|
26308
|
+
}
|
|
26309
|
+
case "template": {
|
|
26310
|
+
const subVerb = rest[0] ?? "";
|
|
26311
|
+
const subRest = rest.slice(1).join(" ").trim();
|
|
26312
|
+
if (subVerb === "" || subVerb === "list") return { message: formatPlanTemplates() };
|
|
26313
|
+
if (subVerb === "use") {
|
|
26314
|
+
if (!subRest) return { message: "Usage: /plan template use <template-name>" };
|
|
26315
|
+
const template = getPlanTemplate(subRest);
|
|
26316
|
+
if (!template) {
|
|
26317
|
+
return {
|
|
26318
|
+
message: `Unknown template "${subRest}". Use /plan template list to see available templates.`
|
|
26319
|
+
};
|
|
26320
|
+
}
|
|
26321
|
+
let updated = plan;
|
|
26322
|
+
for (const item of template.items) {
|
|
26323
|
+
({ plan: updated } = addPlanItem(updated, item.title, item.details));
|
|
26324
|
+
}
|
|
26325
|
+
await savePlan(planPath, updated);
|
|
26326
|
+
return {
|
|
26327
|
+
message: `Applied template "${template.name}" (${template.items.length} items):
|
|
26328
|
+
${formatPlan(updated)}`
|
|
26329
|
+
};
|
|
26330
|
+
}
|
|
26331
|
+
return { message: `Unknown template subcommand "${subVerb}". Try: list | use <name>` };
|
|
26332
|
+
}
|
|
26333
|
+
case "clear": {
|
|
26334
|
+
const updated = clearPlan(plan);
|
|
26335
|
+
await savePlan(planPath, updated);
|
|
26336
|
+
return { message: "Plan cleared." };
|
|
26337
|
+
}
|
|
26338
|
+
default:
|
|
26339
|
+
return {
|
|
26340
|
+
message: `Unknown subcommand "${verb}". Try: show | add <title> | start <id|#> | done <id|#> | remove <id|#> | promote <id|#> | derive <id|#> | template [list|use <name>] | clear`
|
|
26341
|
+
};
|
|
26342
|
+
}
|
|
26343
|
+
}
|
|
26344
|
+
};
|
|
26345
|
+
}
|
|
26346
|
+
|
|
26347
|
+
export { AGENTS_BY_PHASE, AGENT_CATALOG, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, ALL_SYNC_CATEGORIES, AUDIT_LOG_AGENT, Agent, AgentError, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutoPhasePlanner, AutoPhaseRunner, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, CONTEXT_WINDOW_MODES, CheckpointManager, CloudSync, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultPromptStore, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorStateCheckpoint, DoneConditionChecker, ERROR_CODES, EternalAutonomyEngine, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, FsError, GitignoreUpdater, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, MAX_JOURNAL_ENTRIES, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, PhaseGraphBuilder, PhaseOrchestrator, PhaseStore, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, ReportGenerator, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, ScopedEventBus, SddParallelRun, SddTaskDecomposer, SecurityScanner, SecurityScannerOrchestrator, SelectiveCompactor, SessionAnalyzer, SessionError, SkillGenerator, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, TechStackDetector, ToolError, ToolExecutor, ToolRegistry, WorktreeManager, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, appendJournal, applyRosterBudget, asBlocks, asText, assertSafePath, atomicWrite, attachAutoExtend, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildBtwBlock, buildChildEnv, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, color, compileGlob, compileUserRegex, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, consumeBtwNotes, context7Server, contextManagerTool, createAutoExecutor, createAutoPhaseFromTaskGraph, createContextManagerTool, createDefaultPipelines, createDelegateTool, createGitPlugin, createMcpControlTool, createMessage, createObservabilityPlugin, createPlanPlugin, createPromptsPlugin, createSecurityPlugin, createSecuritySlashCommand, createSkillsPlugin, createSyncPlugin, createToolOutputSerializer, decryptConfigSecrets, defaultGitignoreUpdater, defaultOrchestrator, defaultReportGenerator, defaultSecurityScanner, defaultSkillGenerator, defaultTechStackDetector, deriveTodosFromPlanItem, detectNewlineStyle, dispatchAgent, downloadGitHubTarball, emptyGoal, emptyPlan, encryptConfigSecrets, ensureDir, estimateRequestTokens, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, formatContextWindowModeList, formatGoal, formatPlan, formatPlanTemplates, formatTodosList, getAgentDefinition, getContextWindowMode, getPlanTemplate, getTemplate, githubServer, goalFilePath, googleMapsServer, isAgentError, isConfigError, isContextWindowModeId, isFsError, isImageBlock, isPluginError, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, listContextWindowModes, listPlanTemplates, listTemplates, loadDirectorState, loadGoal, loadPlan, loadPlugins, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAutonomyPromptContributor, makeContinueToNextIterationTool, makeDirectorSessionFactory, makeLLMClassifier, matchAny, matchGlob, migratePlaintextSecrets, miniMaxVisionServer, normalizeToLf, parseContinueDirective, parseSkillRef, pendingBtwCount, projectHash, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, resolveContextWindowPolicy, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, saveGoal, savePlan, saveTodosCheckpoint, scoreAgents, securitySlashCommand, sentinelServer, setBtwNote, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, summarizeUsage, templateToMarkdown, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState, zaiVisionServer };
|
|
24901
26348
|
//# sourceMappingURL=index.js.map
|
|
24902
26349
|
//# sourceMappingURL=index.js.map
|