@dosu/cli 0.38.0 → 0.40.0
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/bin/dosu.js +103 -26
- package/package.json +1 -1
package/bin/dosu.js
CHANGED
|
@@ -4759,7 +4759,7 @@ var init_dist4 = __esm(() => {
|
|
|
4759
4759
|
function getVersionString() {
|
|
4760
4760
|
return `v${VERSION}`;
|
|
4761
4761
|
}
|
|
4762
|
-
var VERSION = "0.
|
|
4762
|
+
var VERSION = "0.40.0", INSTALL_CHANNEL = "npm";
|
|
4763
4763
|
|
|
4764
4764
|
// src/debug/logger.ts
|
|
4765
4765
|
import {
|
|
@@ -8178,7 +8178,8 @@ __export(exports_prompts, {
|
|
|
8178
8178
|
buildReadyEnvelope: () => buildReadyEnvelope,
|
|
8179
8179
|
STOP_PREFIX: () => STOP_PREFIX,
|
|
8180
8180
|
SAVE_NUDGE: () => SAVE_NUDGE,
|
|
8181
|
-
LOOKUP_STARTED_NOTE: () => LOOKUP_STARTED_NOTE
|
|
8181
|
+
LOOKUP_STARTED_NOTE: () => LOOKUP_STARTED_NOTE,
|
|
8182
|
+
LATE_RESULT_NOTE: () => LATE_RESULT_NOTE
|
|
8182
8183
|
});
|
|
8183
8184
|
function buildReadyEnvelope(context, saveRecommended = false) {
|
|
8184
8185
|
const trimmed = context.trim();
|
|
@@ -8204,9 +8205,10 @@ function buildReadyEnvelope(context, saveRecommended = false) {
|
|
|
8204
8205
|
|
|
8205
8206
|
`);
|
|
8206
8207
|
}
|
|
8207
|
-
var LOOKUP_STARTED_NOTE, STOP_PREFIX, SAVE_NUDGE;
|
|
8208
|
+
var LOOKUP_STARTED_NOTE, LATE_RESULT_NOTE, STOP_PREFIX, SAVE_NUDGE;
|
|
8208
8209
|
var init_prompts = __esm(() => {
|
|
8209
8210
|
LOOKUP_STARTED_NOTE = "Dosu started a background knowledge lookup for this request. Keep working normally — " + "do not wait or pause for it. If Dosu context arrives during this session, fold it in " + "quietly to work faster and avoid wrong assumptions. Mention Dosu when its context " + "helps you.";
|
|
8211
|
+
LATE_RESULT_NOTE = "Heads-up: the context below answers your PREVIOUS prompt — its lookup finished after " + "that turn had already ended. Apply it only where it is still relevant to the current " + "work; do not treat it as a response to the new prompt.";
|
|
8210
8212
|
STOP_PREFIX = "Dosu knowledge finished after your last action. Re-check your current conclusion against " + "it, then continue or finish — do not redo work it merely confirms.";
|
|
8211
8213
|
SAVE_NUDGE = "Dosu has little or no existing knowledge on this topic. If you uncover something durable — " + "a non-obvious mechanism, a gotcha, or a route through the code — save it with the Dosu " + "`save_topic` tool when you finish, so future sessions start ahead. Only save genuinely " + "reusable knowledge; skip trivial or one-off details.";
|
|
8212
8214
|
});
|
|
@@ -15033,12 +15035,13 @@ init_commander();
|
|
|
15033
15035
|
init_config();
|
|
15034
15036
|
init_logger();
|
|
15035
15037
|
init_prompts();
|
|
15038
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
15036
15039
|
import { existsSync as existsSync11, readFileSync as readFileSync11 } from "node:fs";
|
|
15037
15040
|
import { basename } from "node:path";
|
|
15038
15041
|
|
|
15039
15042
|
// src/hooks/state.ts
|
|
15040
15043
|
init_config();
|
|
15041
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
15044
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync6, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
15042
15045
|
import { dirname as dirname2, join as join5 } from "node:path";
|
|
15043
15046
|
function getStateDir() {
|
|
15044
15047
|
return process.env.DOSU_HOOK_STATE_DIR ?? join5(getConfigDir(), "hooks");
|
|
@@ -15066,9 +15069,24 @@ function saveState(state) {
|
|
|
15066
15069
|
writeFileSync5(file, JSON.stringify(state, null, 2), { mode: 384 });
|
|
15067
15070
|
} catch {}
|
|
15068
15071
|
}
|
|
15069
|
-
function
|
|
15072
|
+
function claimState(sessionId) {
|
|
15073
|
+
const file = stateFile(sessionId);
|
|
15070
15074
|
try {
|
|
15071
|
-
|
|
15075
|
+
renameSync2(file, `${file}.claim`);
|
|
15076
|
+
return JSON.parse(readFileSync6(`${file}.claim`, "utf8"));
|
|
15077
|
+
} catch {
|
|
15078
|
+
return null;
|
|
15079
|
+
}
|
|
15080
|
+
}
|
|
15081
|
+
function releaseState(sessionId, state) {
|
|
15082
|
+
const file = stateFile(sessionId);
|
|
15083
|
+
if (state) {
|
|
15084
|
+
try {
|
|
15085
|
+
writeFileSync5(file, JSON.stringify(state, null, 2), { flag: "wx", mode: 384 });
|
|
15086
|
+
} catch {}
|
|
15087
|
+
}
|
|
15088
|
+
try {
|
|
15089
|
+
rmSync2(`${file}.claim`, { force: true });
|
|
15072
15090
|
} catch {}
|
|
15073
15091
|
}
|
|
15074
15092
|
|
|
@@ -15111,6 +15129,27 @@ function errMsg(err) {
|
|
|
15111
15129
|
function repoSlug(cwd) {
|
|
15112
15130
|
return cwd ? basename(cwd) : undefined;
|
|
15113
15131
|
}
|
|
15132
|
+
function gitContext(cwd) {
|
|
15133
|
+
if (!cwd)
|
|
15134
|
+
return {};
|
|
15135
|
+
const run2 = (args) => {
|
|
15136
|
+
try {
|
|
15137
|
+
const out = execFileSync2("git", args, {
|
|
15138
|
+
cwd,
|
|
15139
|
+
timeout: 1500,
|
|
15140
|
+
encoding: "utf8",
|
|
15141
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
15142
|
+
});
|
|
15143
|
+
return out.toString().trim() || undefined;
|
|
15144
|
+
} catch {
|
|
15145
|
+
return;
|
|
15146
|
+
}
|
|
15147
|
+
};
|
|
15148
|
+
const repo = run2(["remote", "get-url", "origin"]);
|
|
15149
|
+
if (!repo)
|
|
15150
|
+
return {};
|
|
15151
|
+
return { repo, branch: run2(["branch", "--show-current"]) };
|
|
15152
|
+
}
|
|
15114
15153
|
async function runUserPromptSubmit(input, now = Date.now(), agent = DEFAULT_AGENT) {
|
|
15115
15154
|
const sessionId = input.session_id;
|
|
15116
15155
|
if (!sessionId)
|
|
@@ -15128,14 +15167,31 @@ async function runUserPromptSubmit(input, now = Date.now(), agent = DEFAULT_AGEN
|
|
|
15128
15167
|
logger.debug("hooks", `submit sid=${sid8(sessionId)} reuse tid=${existing.ticketId}`);
|
|
15129
15168
|
return;
|
|
15130
15169
|
}
|
|
15131
|
-
|
|
15132
|
-
clearState(sessionId);
|
|
15170
|
+
const claimed = existing ? claimState(sessionId) : null;
|
|
15133
15171
|
const cfg = loadConfig();
|
|
15134
15172
|
if (!cfg.active_account?.target?.api_key || !cfg.active_account?.target?.deployment_id) {
|
|
15173
|
+
if (claimed)
|
|
15174
|
+
releaseState(sessionId, null);
|
|
15135
15175
|
logger.debug("hooks", "submit skipped reason=not-configured");
|
|
15136
15176
|
return;
|
|
15137
15177
|
}
|
|
15178
|
+
const git = gitContext(input.cwd);
|
|
15138
15179
|
const tc = await Promise.resolve().then(() => (init_ticket_client(), exports_ticket_client));
|
|
15180
|
+
let lateBlock;
|
|
15181
|
+
if (claimed && claimed.status === "pending" && now <= claimed.expiresAt) {
|
|
15182
|
+
try {
|
|
15183
|
+
const old = await tc.requestGetTicket(cfg, claimed.ticketId);
|
|
15184
|
+
if (old.status === "ready" && old.result?.context.trim()) {
|
|
15185
|
+
const { buildReadyEnvelope: buildReadyEnvelope2, LATE_RESULT_NOTE: LATE_RESULT_NOTE2 } = await Promise.resolve().then(() => (init_prompts(), exports_prompts));
|
|
15186
|
+
lateBlock = `${LATE_RESULT_NOTE2}
|
|
15187
|
+
|
|
15188
|
+
${buildReadyEnvelope2(old.result.context, old.result.save_recommended ?? false)}`;
|
|
15189
|
+
logger.info("hooks", `harvest tid=${claimed.ticketId} delivered=late`);
|
|
15190
|
+
}
|
|
15191
|
+
} catch {}
|
|
15192
|
+
}
|
|
15193
|
+
if (claimed)
|
|
15194
|
+
releaseState(sessionId, null);
|
|
15139
15195
|
const startedAt = Date.now();
|
|
15140
15196
|
let resp;
|
|
15141
15197
|
try {
|
|
@@ -15145,7 +15201,8 @@ async function runUserPromptSubmit(input, now = Date.now(), agent = DEFAULT_AGEN
|
|
|
15145
15201
|
session_id: sessionId,
|
|
15146
15202
|
turn_id: turnId,
|
|
15147
15203
|
prompt,
|
|
15148
|
-
repo: repoSlug(input.cwd)
|
|
15204
|
+
repo: git.repo ?? repoSlug(input.cwd),
|
|
15205
|
+
branch: git.branch
|
|
15149
15206
|
});
|
|
15150
15207
|
} catch (err) {
|
|
15151
15208
|
logger.warn("hooks", `error event=user-prompt-submit reason=${errMsg(err)}`);
|
|
@@ -15161,7 +15218,9 @@ async function runUserPromptSubmit(input, now = Date.now(), agent = DEFAULT_AGEN
|
|
|
15161
15218
|
});
|
|
15162
15219
|
logger.info("hooks", `create sid=${sid8(sessionId)} tid=${resp.ticket_id} latency=${Date.now() - startedAt}ms`);
|
|
15163
15220
|
const { LOOKUP_STARTED_NOTE: LOOKUP_STARTED_NOTE2 } = await Promise.resolve().then(() => (init_prompts(), exports_prompts));
|
|
15164
|
-
printHookContext("UserPromptSubmit",
|
|
15221
|
+
printHookContext("UserPromptSubmit", lateBlock ? `${lateBlock}
|
|
15222
|
+
|
|
15223
|
+
${LOOKUP_STARTED_NOTE2}` : LOOKUP_STARTED_NOTE2);
|
|
15165
15224
|
}
|
|
15166
15225
|
async function runPostToolUse(input, now = Date.now()) {
|
|
15167
15226
|
const sessionId = input.session_id;
|
|
@@ -15189,35 +15248,45 @@ async function runPostToolUse(input, now = Date.now()) {
|
|
|
15189
15248
|
saveState({ ...state, lastCheckedAt: now });
|
|
15190
15249
|
return;
|
|
15191
15250
|
}
|
|
15251
|
+
const claimed = claimState(sessionId);
|
|
15252
|
+
if (!claimed) {
|
|
15253
|
+
logger.debug("hooks", `poll tid=${state.ticketId} skipped=contended`);
|
|
15254
|
+
return;
|
|
15255
|
+
}
|
|
15192
15256
|
const tc = await Promise.resolve().then(() => (init_ticket_client(), exports_ticket_client));
|
|
15193
15257
|
const startedAt = Date.now();
|
|
15194
15258
|
let resp;
|
|
15195
15259
|
try {
|
|
15196
|
-
resp = await tc.requestGetTicket(cfg,
|
|
15260
|
+
resp = await tc.requestGetTicket(cfg, claimed.ticketId);
|
|
15197
15261
|
} catch (err) {
|
|
15198
15262
|
if (tc.isDefinitiveError(err)) {
|
|
15199
|
-
|
|
15263
|
+
releaseState(sessionId, { ...claimed, status: "failed", lastCheckedAt: now });
|
|
15200
15264
|
logger.warn("hooks", `error event=post-tool-use reason=${errMsg(err)} definitive`);
|
|
15201
15265
|
} else {
|
|
15202
|
-
|
|
15203
|
-
logger.debug("hooks", `poll tid=${
|
|
15266
|
+
releaseState(sessionId, { ...claimed, lastCheckedAt: now });
|
|
15267
|
+
logger.debug("hooks", `poll tid=${claimed.ticketId} transient-error`);
|
|
15204
15268
|
}
|
|
15205
15269
|
return;
|
|
15206
15270
|
}
|
|
15207
15271
|
if (resp.status === "pending") {
|
|
15208
|
-
|
|
15209
|
-
logger.debug("hooks", `poll tid=${
|
|
15272
|
+
releaseState(sessionId, { ...claimed, lastCheckedAt: now });
|
|
15273
|
+
logger.debug("hooks", `poll tid=${claimed.ticketId} status=pending`);
|
|
15210
15274
|
return;
|
|
15211
15275
|
}
|
|
15212
15276
|
if (resp.status === "failed" || resp.status === "expired") {
|
|
15213
|
-
|
|
15277
|
+
releaseState(sessionId, { ...claimed, status: resp.status, lastCheckedAt: now });
|
|
15214
15278
|
return;
|
|
15215
15279
|
}
|
|
15216
15280
|
if (!resp.result) {
|
|
15217
|
-
|
|
15281
|
+
releaseState(sessionId, { ...claimed, status: "failed", lastCheckedAt: now });
|
|
15218
15282
|
return;
|
|
15219
15283
|
}
|
|
15220
|
-
|
|
15284
|
+
releaseState(sessionId, {
|
|
15285
|
+
...claimed,
|
|
15286
|
+
status: "delivered",
|
|
15287
|
+
deliveredAt: now,
|
|
15288
|
+
lastCheckedAt: now
|
|
15289
|
+
});
|
|
15221
15290
|
const { buildReadyEnvelope: buildReadyEnvelope2 } = await Promise.resolve().then(() => (init_prompts(), exports_prompts));
|
|
15222
15291
|
const context = buildReadyEnvelope2(resp.result.context, resp.result.save_recommended ?? false);
|
|
15223
15292
|
if (context) {
|
|
@@ -15239,37 +15308,45 @@ async function runStop(input, now = Date.now()) {
|
|
|
15239
15308
|
const cfg = loadConfig();
|
|
15240
15309
|
if (!cfg.active_account?.target?.api_key || !cfg.active_account?.target?.deployment_id)
|
|
15241
15310
|
return printContinue();
|
|
15311
|
+
const claimed = claimState(sessionId);
|
|
15312
|
+
if (!claimed)
|
|
15313
|
+
return printContinue();
|
|
15242
15314
|
const tc = await Promise.resolve().then(() => (init_ticket_client(), exports_ticket_client));
|
|
15243
15315
|
const pollMs = stopPollMs();
|
|
15244
15316
|
const maxWaits = pollMs > 0 ? Math.floor(stopWaitMs() / pollMs) : 0;
|
|
15245
15317
|
for (let waited = 0;; waited++) {
|
|
15246
15318
|
let resp;
|
|
15247
15319
|
try {
|
|
15248
|
-
resp = await tc.requestGetTicket(cfg,
|
|
15320
|
+
resp = await tc.requestGetTicket(cfg, claimed.ticketId);
|
|
15249
15321
|
} catch {
|
|
15250
|
-
|
|
15322
|
+
releaseState(sessionId, { ...claimed, lastCheckedAt: now });
|
|
15251
15323
|
return printContinue();
|
|
15252
15324
|
}
|
|
15253
15325
|
if (resp.status === "ready" && resp.result) {
|
|
15254
|
-
|
|
15326
|
+
releaseState(sessionId, {
|
|
15327
|
+
...claimed,
|
|
15328
|
+
status: "delivered",
|
|
15329
|
+
deliveredAt: now,
|
|
15330
|
+
lastCheckedAt: now
|
|
15331
|
+
});
|
|
15255
15332
|
if (resp.result.context.trim()) {
|
|
15256
15333
|
const { buildReadyEnvelope: buildReadyEnvelope2, STOP_PREFIX: STOP_PREFIX2 } = await Promise.resolve().then(() => (init_prompts(), exports_prompts));
|
|
15257
15334
|
const envelope = buildReadyEnvelope2(resp.result.context, resp.result.save_recommended ?? false);
|
|
15258
15335
|
console.log(JSON.stringify({ decision: "block", reason: `${STOP_PREFIX2}
|
|
15259
15336
|
|
|
15260
15337
|
${envelope}` }));
|
|
15261
|
-
logger.info("hooks", `stop tid=${
|
|
15338
|
+
logger.info("hooks", `stop tid=${claimed.ticketId} delivered=true`);
|
|
15262
15339
|
return;
|
|
15263
15340
|
}
|
|
15264
|
-
logger.debug("hooks", `stop tid=${
|
|
15341
|
+
logger.debug("hooks", `stop tid=${claimed.ticketId} delivered=true gap-no-block`);
|
|
15265
15342
|
return printContinue();
|
|
15266
15343
|
}
|
|
15267
15344
|
if (resp.status !== "pending" || waited >= maxWaits)
|
|
15268
15345
|
break;
|
|
15269
15346
|
await sleep4(pollMs);
|
|
15270
15347
|
}
|
|
15271
|
-
|
|
15272
|
-
logger.debug("hooks", `stop tid=${
|
|
15348
|
+
releaseState(sessionId, { ...claimed, lastCheckedAt: now });
|
|
15349
|
+
logger.debug("hooks", `stop tid=${claimed.ticketId} delivered=false`);
|
|
15273
15350
|
printContinue();
|
|
15274
15351
|
}
|
|
15275
15352
|
function runStatus(input, opts, now = Date.now()) {
|