@dosu/cli 0.39.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.
Files changed (2) hide show
  1. package/bin/dosu.js +78 -25
  2. 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.39.0", INSTALL_CHANNEL = "npm";
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
  });
@@ -15039,7 +15041,7 @@ import { basename } from "node:path";
15039
15041
 
15040
15042
  // src/hooks/state.ts
15041
15043
  init_config();
15042
- 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";
15043
15045
  import { dirname as dirname2, join as join5 } from "node:path";
15044
15046
  function getStateDir() {
15045
15047
  return process.env.DOSU_HOOK_STATE_DIR ?? join5(getConfigDir(), "hooks");
@@ -15067,9 +15069,24 @@ function saveState(state) {
15067
15069
  writeFileSync5(file, JSON.stringify(state, null, 2), { mode: 384 });
15068
15070
  } catch {}
15069
15071
  }
15070
- function clearState(sessionId) {
15072
+ function claimState(sessionId) {
15073
+ const file = stateFile(sessionId);
15071
15074
  try {
15072
- rmSync2(stateFile(sessionId), { force: true });
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 });
15073
15090
  } catch {}
15074
15091
  }
15075
15092
 
@@ -15150,15 +15167,31 @@ async function runUserPromptSubmit(input, now = Date.now(), agent = DEFAULT_AGEN
15150
15167
  logger.debug("hooks", `submit sid=${sid8(sessionId)} reuse tid=${existing.ticketId}`);
15151
15168
  return;
15152
15169
  }
15153
- if (existing)
15154
- clearState(sessionId);
15170
+ const claimed = existing ? claimState(sessionId) : null;
15155
15171
  const cfg = loadConfig();
15156
15172
  if (!cfg.active_account?.target?.api_key || !cfg.active_account?.target?.deployment_id) {
15173
+ if (claimed)
15174
+ releaseState(sessionId, null);
15157
15175
  logger.debug("hooks", "submit skipped reason=not-configured");
15158
15176
  return;
15159
15177
  }
15160
15178
  const git = gitContext(input.cwd);
15161
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);
15162
15195
  const startedAt = Date.now();
15163
15196
  let resp;
15164
15197
  try {
@@ -15185,7 +15218,9 @@ async function runUserPromptSubmit(input, now = Date.now(), agent = DEFAULT_AGEN
15185
15218
  });
15186
15219
  logger.info("hooks", `create sid=${sid8(sessionId)} tid=${resp.ticket_id} latency=${Date.now() - startedAt}ms`);
15187
15220
  const { LOOKUP_STARTED_NOTE: LOOKUP_STARTED_NOTE2 } = await Promise.resolve().then(() => (init_prompts(), exports_prompts));
15188
- printHookContext("UserPromptSubmit", LOOKUP_STARTED_NOTE2);
15221
+ printHookContext("UserPromptSubmit", lateBlock ? `${lateBlock}
15222
+
15223
+ ${LOOKUP_STARTED_NOTE2}` : LOOKUP_STARTED_NOTE2);
15189
15224
  }
15190
15225
  async function runPostToolUse(input, now = Date.now()) {
15191
15226
  const sessionId = input.session_id;
@@ -15213,35 +15248,45 @@ async function runPostToolUse(input, now = Date.now()) {
15213
15248
  saveState({ ...state, lastCheckedAt: now });
15214
15249
  return;
15215
15250
  }
15251
+ const claimed = claimState(sessionId);
15252
+ if (!claimed) {
15253
+ logger.debug("hooks", `poll tid=${state.ticketId} skipped=contended`);
15254
+ return;
15255
+ }
15216
15256
  const tc = await Promise.resolve().then(() => (init_ticket_client(), exports_ticket_client));
15217
15257
  const startedAt = Date.now();
15218
15258
  let resp;
15219
15259
  try {
15220
- resp = await tc.requestGetTicket(cfg, state.ticketId);
15260
+ resp = await tc.requestGetTicket(cfg, claimed.ticketId);
15221
15261
  } catch (err) {
15222
15262
  if (tc.isDefinitiveError(err)) {
15223
- saveState({ ...state, status: "failed", lastCheckedAt: now });
15263
+ releaseState(sessionId, { ...claimed, status: "failed", lastCheckedAt: now });
15224
15264
  logger.warn("hooks", `error event=post-tool-use reason=${errMsg(err)} definitive`);
15225
15265
  } else {
15226
- saveState({ ...state, lastCheckedAt: now });
15227
- logger.debug("hooks", `poll tid=${state.ticketId} transient-error`);
15266
+ releaseState(sessionId, { ...claimed, lastCheckedAt: now });
15267
+ logger.debug("hooks", `poll tid=${claimed.ticketId} transient-error`);
15228
15268
  }
15229
15269
  return;
15230
15270
  }
15231
15271
  if (resp.status === "pending") {
15232
- saveState({ ...state, lastCheckedAt: now });
15233
- logger.debug("hooks", `poll tid=${state.ticketId} status=pending`);
15272
+ releaseState(sessionId, { ...claimed, lastCheckedAt: now });
15273
+ logger.debug("hooks", `poll tid=${claimed.ticketId} status=pending`);
15234
15274
  return;
15235
15275
  }
15236
15276
  if (resp.status === "failed" || resp.status === "expired") {
15237
- saveState({ ...state, status: resp.status, lastCheckedAt: now });
15277
+ releaseState(sessionId, { ...claimed, status: resp.status, lastCheckedAt: now });
15238
15278
  return;
15239
15279
  }
15240
15280
  if (!resp.result) {
15241
- saveState({ ...state, status: "failed", lastCheckedAt: now });
15281
+ releaseState(sessionId, { ...claimed, status: "failed", lastCheckedAt: now });
15242
15282
  return;
15243
15283
  }
15244
- saveState({ ...state, status: "delivered", deliveredAt: now, lastCheckedAt: now });
15284
+ releaseState(sessionId, {
15285
+ ...claimed,
15286
+ status: "delivered",
15287
+ deliveredAt: now,
15288
+ lastCheckedAt: now
15289
+ });
15245
15290
  const { buildReadyEnvelope: buildReadyEnvelope2 } = await Promise.resolve().then(() => (init_prompts(), exports_prompts));
15246
15291
  const context = buildReadyEnvelope2(resp.result.context, resp.result.save_recommended ?? false);
15247
15292
  if (context) {
@@ -15263,37 +15308,45 @@ async function runStop(input, now = Date.now()) {
15263
15308
  const cfg = loadConfig();
15264
15309
  if (!cfg.active_account?.target?.api_key || !cfg.active_account?.target?.deployment_id)
15265
15310
  return printContinue();
15311
+ const claimed = claimState(sessionId);
15312
+ if (!claimed)
15313
+ return printContinue();
15266
15314
  const tc = await Promise.resolve().then(() => (init_ticket_client(), exports_ticket_client));
15267
15315
  const pollMs = stopPollMs();
15268
15316
  const maxWaits = pollMs > 0 ? Math.floor(stopWaitMs() / pollMs) : 0;
15269
15317
  for (let waited = 0;; waited++) {
15270
15318
  let resp;
15271
15319
  try {
15272
- resp = await tc.requestGetTicket(cfg, state.ticketId);
15320
+ resp = await tc.requestGetTicket(cfg, claimed.ticketId);
15273
15321
  } catch {
15274
- saveState({ ...state, lastCheckedAt: now });
15322
+ releaseState(sessionId, { ...claimed, lastCheckedAt: now });
15275
15323
  return printContinue();
15276
15324
  }
15277
15325
  if (resp.status === "ready" && resp.result) {
15278
- saveState({ ...state, status: "delivered", deliveredAt: now, lastCheckedAt: now });
15326
+ releaseState(sessionId, {
15327
+ ...claimed,
15328
+ status: "delivered",
15329
+ deliveredAt: now,
15330
+ lastCheckedAt: now
15331
+ });
15279
15332
  if (resp.result.context.trim()) {
15280
15333
  const { buildReadyEnvelope: buildReadyEnvelope2, STOP_PREFIX: STOP_PREFIX2 } = await Promise.resolve().then(() => (init_prompts(), exports_prompts));
15281
15334
  const envelope = buildReadyEnvelope2(resp.result.context, resp.result.save_recommended ?? false);
15282
15335
  console.log(JSON.stringify({ decision: "block", reason: `${STOP_PREFIX2}
15283
15336
 
15284
15337
  ${envelope}` }));
15285
- logger.info("hooks", `stop tid=${state.ticketId} delivered=true`);
15338
+ logger.info("hooks", `stop tid=${claimed.ticketId} delivered=true`);
15286
15339
  return;
15287
15340
  }
15288
- logger.debug("hooks", `stop tid=${state.ticketId} delivered=true gap-no-block`);
15341
+ logger.debug("hooks", `stop tid=${claimed.ticketId} delivered=true gap-no-block`);
15289
15342
  return printContinue();
15290
15343
  }
15291
15344
  if (resp.status !== "pending" || waited >= maxWaits)
15292
15345
  break;
15293
15346
  await sleep4(pollMs);
15294
15347
  }
15295
- saveState({ ...state, lastCheckedAt: now });
15296
- logger.debug("hooks", `stop tid=${state.ticketId} delivered=false`);
15348
+ releaseState(sessionId, { ...claimed, lastCheckedAt: now });
15349
+ logger.debug("hooks", `stop tid=${claimed.ticketId} delivered=false`);
15297
15350
  printContinue();
15298
15351
  }
15299
15352
  function runStatus(input, opts, now = Date.now()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dosu/cli",
3
- "version": "0.39.0",
3
+ "version": "0.40.0",
4
4
  "type": "module",
5
5
  "description": "Dosu CLI - Manage MCP servers for AI tools",
6
6
  "license": "MIT",