@logicseek/hello-cc 1.0.0 → 1.0.2-dsh.3

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 (123) hide show
  1. package/CHANGELOG.md +149 -0
  2. package/README.md +36 -0
  3. package/README.zh-CN.md +35 -0
  4. package/docs/README.md +9 -0
  5. package/docs/README.zh-CN.md +9 -0
  6. package/docs/commands.md +53 -0
  7. package/docs/commands.zh-CN.md +48 -0
  8. package/docs/dsh.md +137 -0
  9. package/docs/dsh.zh-CN.md +139 -0
  10. package/docs/guide.md +58 -0
  11. package/docs/guide.zh-CN.md +55 -0
  12. package/docs/mcp.md +9 -0
  13. package/docs/mcp.zh-CN.md +23 -0
  14. package/docs/native.md +295 -0
  15. package/docs/native.zh-CN.md +253 -0
  16. package/docs/web-handoff.zh-CN.md +53 -0
  17. package/lib/cli/app.mjs +27 -6
  18. package/lib/cli/commands/coordination.mjs +3 -1
  19. package/lib/cli/commands/doctor.mjs +17 -2
  20. package/lib/cli/commands/dsh.mjs +79 -0
  21. package/lib/cli/commands/gc.mjs +182 -32
  22. package/lib/cli/commands/hook.mjs +241 -90
  23. package/lib/cli/commands/lock.mjs +34 -19
  24. package/lib/cli/commands/mcp.mjs +28 -0
  25. package/lib/cli/commands/misc.mjs +15 -0
  26. package/lib/cli/commands/native.mjs +130 -0
  27. package/lib/cli/commands/peer.mjs +21 -5
  28. package/lib/cli/commands/run.mjs +66 -21
  29. package/lib/cli/commands/task.mjs +6 -3
  30. package/lib/cli-args.mjs +4 -0
  31. package/lib/coordination-state.mjs +15 -1
  32. package/lib/core/coordination/event-retention.mjs +14 -2
  33. package/lib/core/coordination/gc-plan.mjs +3 -13
  34. package/lib/core/coordination/locks.mjs +45 -0
  35. package/lib/core/coordination/results.mjs +79 -0
  36. package/lib/core/peers/bindings.mjs +9 -3
  37. package/lib/diagnostics/codex.mjs +121 -0
  38. package/lib/integrations/codex-interactions.mjs +114 -0
  39. package/lib/integrations/dsh-collaboration.mjs +170 -0
  40. package/lib/integrations/dsh-cordis.d.ts +19 -0
  41. package/lib/integrations/dsh-cordis.mjs +177 -0
  42. package/lib/integrations/dsh.bundle.yml +7 -0
  43. package/lib/integrations/dsh.mjs +287 -0
  44. package/lib/integrations/native/claude.mjs +451 -0
  45. package/lib/integrations/native/codex.mjs +242 -0
  46. package/lib/integrations/native/dsh-acp.mjs +343 -0
  47. package/lib/integrations/native/index.mjs +19 -0
  48. package/lib/integrations/native/interactions.mjs +53 -0
  49. package/lib/integrations/native/jsonrpc.mjs +185 -0
  50. package/lib/integrations/peers/identity.mjs +36 -10
  51. package/lib/integrations/peers/processes.mjs +22 -8
  52. package/lib/integrations/providers.mjs +6 -2
  53. package/lib/mcp/scope.mjs +104 -0
  54. package/lib/mcp/stdio.mjs +80 -0
  55. package/lib/mcp/tools.mjs +197 -0
  56. package/lib/process/identity.mjs +33 -1
  57. package/lib/release/release-notes.mjs +9 -0
  58. package/lib/runtime/buffer-directory-lease.mjs +2 -1
  59. package/lib/runtime/client.mjs +3 -0
  60. package/lib/runtime/native/client.mjs +38 -0
  61. package/lib/runtime/native/service.mjs +511 -0
  62. package/lib/runtime/native/store.mjs +121 -0
  63. package/lib/shared/file-lock.mjs +11 -4
  64. package/lib/shared/socket-lock-worker.mjs +73 -24
  65. package/lib/terminal/tmux.mjs +53 -23
  66. package/lib/ui/help.mjs +5 -2
  67. package/lib/web/adoption-state.mjs +55 -0
  68. package/lib/web/auto-attach.mjs +18 -2
  69. package/lib/web/buffer-gc-runtime.mjs +2 -2
  70. package/lib/web/codex-app-server.mjs +557 -0
  71. package/lib/web/codex-sessions.mjs +287 -0
  72. package/lib/web/control-lease.mjs +204 -0
  73. package/lib/web/http-routes.mjs +197 -15
  74. package/lib/web/http.mjs +5 -4
  75. package/lib/web/native-sessions.mjs +286 -0
  76. package/lib/web/project-contexts.mjs +8 -2
  77. package/lib/web/pty-sessions.mjs +60 -33
  78. package/lib/web/runtime-main.mjs +101 -6
  79. package/lib/web/session-serialize.mjs +0 -0
  80. package/lib/web/startup.mjs +4 -2
  81. package/lib/web/task-results.mjs +76 -0
  82. package/lib/web/tmux-sessions.mjs +92 -11
  83. package/lib/web/tmux-stream.mjs +2 -2
  84. package/lib/web/ui-codex.mjs +550 -0
  85. package/lib/web/ui-command-palette.mjs +119 -0
  86. package/lib/web/ui-handoff.mjs +102 -0
  87. package/lib/web/ui-history.mjs +118 -0
  88. package/lib/web/ui-i18n.mjs +841 -0
  89. package/lib/web/ui-interactions.mjs +78 -0
  90. package/lib/web/ui-login.mjs +101 -0
  91. package/lib/web/ui-native.mjs +165 -0
  92. package/lib/web/ui-preferences.mjs +278 -0
  93. package/lib/web/ui-review.mjs +132 -0
  94. package/lib/web/ui-template.mjs +1106 -576
  95. package/lib/web/ui-terminal-find.mjs +98 -0
  96. package/lib/web/ui-workbench.mjs +94 -0
  97. package/lib/web/ui-workspace.mjs +222 -0
  98. package/package.json +12 -4
  99. package/scripts/check-free-identifiers.mjs +67 -8
  100. package/scripts/codex-diagnostics-installed-acceptance.mjs +72 -0
  101. package/scripts/dsh-cordis-acceptance.mjs +200 -0
  102. package/scripts/dsh-installed-acceptance.mjs +247 -0
  103. package/scripts/dsh-official-acceptance.mjs +180 -0
  104. package/scripts/dsh-web-startup-acceptance.mjs +87 -0
  105. package/scripts/github-release.mjs +3 -1
  106. package/scripts/native-live-acceptance.mjs +369 -0
  107. package/scripts/regression.mjs +338 -187
  108. package/scripts/web-handoff-installed-acceptance.mjs +98 -0
  109. package/scripts/web-native-interaction-acceptance.mjs +204 -0
  110. package/scripts/web-native-model-acceptance.mjs +138 -0
  111. package/docs/architecture-split-review.md +0 -217
  112. package/docs/current-state-review-2026-08.md +0 -341
  113. package/docs/defect-review-2026-07.md +0 -382
  114. package/docs/layout-split-stack-audit.md +0 -298
  115. package/docs/superpowers/plans/2026-08-03-runtime-safety-plan.md +0 -294
  116. package/docs/superpowers/plans/2026-08-03-v1-data-identity-plan.md +0 -400
  117. package/docs/superpowers/plans/2026-08-03-v1-release-verification-plan.md +0 -284
  118. package/docs/superpowers/plans/2026-08-03-web-api-hardening-plan.md +0 -303
  119. package/docs/superpowers/plans/2026-08-04-runtime-buffer-gc-two-phase.md +0 -71
  120. package/docs/superpowers/specs/2026-08-03-runtime-safety-design.md +0 -117
  121. package/docs/superpowers/specs/2026-08-03-v1-data-identity-design.md +0 -120
  122. package/docs/superpowers/specs/2026-08-03-v1-release-verification-design.md +0 -117
  123. package/docs/superpowers/specs/2026-08-03-web-api-hardening-design.md +0 -104
@@ -39,6 +39,8 @@ import {
39
39
  } from '../../runtime/buffer-gc.mjs';
40
40
  import { applyClockSafeBufferPlan } from '../../runtime/buffer-gc-protocol.mjs';
41
41
 
42
+ export const RUNTIME_BUFFER_GC_TIMEOUT_MS = 30_000;
43
+
42
44
  export function createGcCommands(deps) {
43
45
  const {
44
46
  connect, now, helpGc,
@@ -109,7 +111,8 @@ function observeGcClockSafety(ctx, db, {
109
111
  observedAt,
110
112
  historyCategories,
111
113
  includeStalePeers,
112
- historySnapshot
114
+ historySnapshot,
115
+ bufferPathGuard = null
113
116
  }) {
114
117
  try {
115
118
  return runOptimisticEvidenceMutation(db, {
@@ -124,10 +127,13 @@ function observeGcClockSafety(ctx, db, {
124
127
  }),
125
128
  observe: (subject) => observeGcEvidence(ctx, subject),
126
129
  same: (left, right) => JSON.stringify(left) === JSON.stringify(right),
127
- beforeMutate: (subject, evidenceByOwner) => observeClockSafetyInTransactionOrThrow(
128
- db,
129
- gcClockSafetyOptions(subject, evidenceByOwner, olderThanDays)
130
- ),
130
+ beforeMutate: (subject, evidenceByOwner) => {
131
+ if (bufferPathGuard) assertBufferDirectoryPathUnchanged(bufferPathGuard);
132
+ return observeClockSafetyInTransactionOrThrow(
133
+ db,
134
+ gcClockSafetyOptions(subject, evidenceByOwner, olderThanDays)
135
+ );
136
+ },
131
137
  changedMessage: 'GC subjects changed while clock evidence was being observed; retry',
132
138
  mutate: (subject, evidenceByOwner, observation) => ({
133
139
  ...observation,
@@ -192,6 +198,128 @@ function previewGcClockSafety(ctx, db, options) {
192
198
  }
193
199
  }
194
200
 
201
+ function bufferDirectoryPathForbidden(message, directory) {
202
+ return new CliError('PROJECT_PATH_FORBIDDEN', `${message}: ${directory}`);
203
+ }
204
+
205
+ function assertBufferDirectoryPathUnchanged(captured) {
206
+ let resolvedRoot;
207
+ try {
208
+ resolvedRoot = fs.realpathSync.native(captured.requestedRoot);
209
+ } catch (error) {
210
+ if (error?.code !== 'ENOENT') throw error;
211
+ throw bufferDirectoryPathForbidden(
212
+ 'Project root changed during buffer GC',
213
+ captured.requestedRoot
214
+ );
215
+ }
216
+ if (resolvedRoot !== captured.canonicalRoot) {
217
+ throw bufferDirectoryPathForbidden(
218
+ 'Project root changed during buffer GC',
219
+ captured.requestedRoot
220
+ );
221
+ }
222
+ for (const component of captured.components) {
223
+ let current;
224
+ try {
225
+ current = fs.lstatSync(component.path);
226
+ } catch (error) {
227
+ if (error?.code !== 'ENOENT') throw error;
228
+ throw bufferDirectoryPathForbidden(
229
+ 'Buffer directory path changed during lease acquisition',
230
+ captured.directory
231
+ );
232
+ }
233
+ if (current.isSymbolicLink() || !current.isDirectory() ||
234
+ current.dev !== component.stat.dev || current.ino !== component.stat.ino) {
235
+ throw bufferDirectoryPathForbidden(
236
+ 'Buffer directory path changed during lease acquisition',
237
+ captured.directory
238
+ );
239
+ }
240
+ }
241
+ if (!captured.exists) {
242
+ try {
243
+ fs.lstatSync(captured.directory);
244
+ } catch (error) {
245
+ if (error?.code === 'ENOENT') return;
246
+ throw error;
247
+ }
248
+ throw bufferDirectoryPathForbidden(
249
+ 'Buffer directory path changed during lease acquisition',
250
+ captured.directory
251
+ );
252
+ }
253
+ }
254
+
255
+ function captureRequiredBufferDirectoryComponent(component, description) {
256
+ const stat = fs.lstatSync(component);
257
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
258
+ throw bufferDirectoryPathForbidden(`${description} must be a real directory`, component);
259
+ }
260
+ return { path: component, stat };
261
+ }
262
+
263
+ function captureOptionalBufferDirectory(root, directoryName) {
264
+ const requestedRoot = path.resolve(root);
265
+ const canonicalRoot = fs.realpathSync.native(requestedRoot);
266
+ const stateDirectory = path.join(canonicalRoot, '.hello-cc');
267
+ const directory = path.join(stateDirectory, directoryName);
268
+ const components = [
269
+ captureRequiredBufferDirectoryComponent(canonicalRoot, 'Project root'),
270
+ captureRequiredBufferDirectoryComponent(stateDirectory, 'Project state directory')
271
+ ];
272
+
273
+ let stat;
274
+ try {
275
+ stat = fs.lstatSync(directory);
276
+ } catch (error) {
277
+ if (error?.code === 'ENOENT') {
278
+ const captured = {
279
+ requestedRoot,
280
+ canonicalRoot,
281
+ directory,
282
+ components,
283
+ exists: false
284
+ };
285
+ assertBufferDirectoryPathUnchanged(captured);
286
+ return captured;
287
+ }
288
+ throw error;
289
+ }
290
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
291
+ throw bufferDirectoryPathForbidden('Buffer directory must be a real directory', directory);
292
+ }
293
+ components.push({ path: directory, stat });
294
+ return { requestedRoot, canonicalRoot, directory, components, exists: true };
295
+ }
296
+
297
+ function collectGuardedBufferEvidence(collector, captured) {
298
+ const evidence = collector(captured.directory);
299
+ assertBufferDirectoryPathUnchanged(captured);
300
+ return evidence;
301
+ }
302
+
303
+ function withOptionalBufferDirectoryLease(captured, callback) {
304
+ assertBufferDirectoryPathUnchanged(captured);
305
+ if (!captured.exists) return null;
306
+ return withBufferDirectoryLease(captured.directory, (leasedDirectory) => {
307
+ const assertDirectoryUnchanged = () => {
308
+ assertBufferDirectoryPathUnchanged(captured);
309
+ if (fs.realpathSync.native(captured.directory) !== leasedDirectory) {
310
+ throw bufferDirectoryPathForbidden(
311
+ 'Buffer directory path changed during lease acquisition',
312
+ captured.directory
313
+ );
314
+ }
315
+ };
316
+ assertDirectoryUnchanged();
317
+ const result = callback(leasedDirectory, assertDirectoryUnchanged);
318
+ assertDirectoryUnchanged();
319
+ return result;
320
+ }, { createParent: false });
321
+ }
322
+
195
323
  function runGc(ctx, db, {
196
324
  olderThanDays = 7,
197
325
  dryRun = false,
@@ -203,6 +331,9 @@ function runGc(ctx, db, {
203
331
  history = false,
204
332
  observedAt = null
205
333
  } = {}) {
334
+ const bufferPathGuard = skipBufferFiles
335
+ ? null
336
+ : captureOptionalBufferDirectory(ctx.root, BUFS_DIR_NAME);
206
337
  const auto = scope === 'auto';
207
338
  const historyCategories = auto ? ['events'] : ['events', 'tasks', 'messages', 'handoffs'];
208
339
  const applyHistory = auto || history;
@@ -213,16 +344,19 @@ function runGc(ctx, db, {
213
344
  : gcNow * 1000 - (auto ? Math.min(olderThanDays, 7) : olderThanDays) * 86400000;
214
345
  const bufferRetentionSec = (auto ? Math.min(olderThanDays, 7) : olderThanDays) * 86400;
215
346
  const preparedBufferEvidence = !skipBufferFiles && collectBufferEvidenceNow
216
- ? collectBufferEvidenceNow()
347
+ ? collectGuardedBufferEvidence(collectBufferEvidenceNow, bufferPathGuard)
217
348
  : null;
218
- const bufferGcCutoffs = bufferDirectoryPaths(bufferDirectory(ctx)).flatMap((file) => {
219
- try {
220
- const stat = fs.lstatSync(file);
221
- return [Math.max(0, Math.floor(stat.mtimeMs / 1000) + bufferRetentionSec)];
222
- } catch {
223
- return [];
224
- }
225
- });
349
+ const bufferGcCutoffs = skipBufferFiles
350
+ ? []
351
+ : bufferDirectoryPaths(bufferPathGuard.directory).flatMap((file) => {
352
+ try {
353
+ const stat = fs.lstatSync(file);
354
+ return [Math.max(0, Math.floor(stat.mtimeMs / 1000) + bufferRetentionSec)];
355
+ } catch {
356
+ return [];
357
+ }
358
+ });
359
+ if (bufferPathGuard) assertBufferDirectoryPathUnchanged(bufferPathGuard);
226
360
  bufferGcCutoffs.push(...(preparedBufferEvidence?.gcCutoffs || []));
227
361
  const historySnapshot = createHistoryGcSnapshot(db, gcNow - retentionSec, {
228
362
  categories: historyCategories,
@@ -245,7 +379,8 @@ function runGc(ctx, db, {
245
379
  bufferGcCutoffs,
246
380
  historyCategories,
247
381
  gcNow,
248
- historySnapshot
382
+ historySnapshot,
383
+ bufferPathGuard
249
384
  }),
250
385
  'History GC run'
251
386
  );
@@ -266,6 +401,7 @@ function runGcWithHistorySnapshot(ctx, db, {
266
401
  historyCategories,
267
402
  gcNow,
268
403
  historySnapshot,
404
+ bufferPathGuard = null,
269
405
  clockObservation: preparedClockObservation = null,
270
406
  forceDefer = false
271
407
  }) {
@@ -275,17 +411,23 @@ function runGcWithHistorySnapshot(ctx, db, {
275
411
  observedAt: gcNow,
276
412
  historyCategories,
277
413
  includeStalePeers: !auto,
278
- historySnapshot
414
+ historySnapshot,
415
+ bufferPathGuard
279
416
  });
280
417
  const graceActive = forceDefer || clockGraceSuppressed(gcNow, clockObservation.graceUntil);
281
418
  let databaseGraceActive = graceActive;
282
419
  const beforeDatabaseMutate = () => {
420
+ if (bufferPathGuard) assertBufferDirectoryPathUnchanged(bufferPathGuard);
283
421
  if (databaseGraceActive) return false;
284
422
  try {
285
423
  const observedAt = now();
286
424
  const allowed = !clockGraceSuppressed(observedAt, readClockGraceUntil(db));
287
- if (!allowed) databaseGraceActive = true;
288
- return allowed;
425
+ if (!allowed) {
426
+ databaseGraceActive = true;
427
+ return false;
428
+ }
429
+ if (bufferPathGuard) assertBufferDirectoryPathUnchanged(bufferPathGuard);
430
+ return true;
289
431
  } catch (error) {
290
432
  if (error instanceof CliError) throw error;
291
433
  throw clockSafetyUnavailable(error);
@@ -325,14 +467,16 @@ function runGcWithHistorySnapshot(ctx, db, {
325
467
  const bufCutoffMs = Number.isFinite(bufferCutoffMs)
326
468
  ? bufferCutoffMs
327
469
  : gcNow * 1000 - (auto ? Math.min(olderThanDays, 7) : olderThanDays) * 86400000;
328
- const bufsDir = path.join(ctx.root, '.hello-cc', BUFS_DIR_NAME);
329
470
  if (!graceActive && !skipBufferFiles) {
330
- const bufferResult = withBufferDirectoryLease(bufsDir, () => {
471
+ const bufferResult = withOptionalBufferDirectoryLease(bufferPathGuard, (
472
+ leasedBufsDir,
473
+ assertDirectoryUnchanged
474
+ ) => {
331
475
  const currentEvidence = collectBufferEvidenceNow
332
- ? collectBufferEvidenceNow()
476
+ ? collectGuardedBufferEvidence(collectBufferEvidenceNow, bufferPathGuard)
333
477
  : preparedBufferEvidence || { protectedPaths: protectedBuffers, unknownPaths: new Set(), gcCutoffs: [] };
334
478
  const currentPlan = planBufferFiles({
335
- directories: [bufsDir],
479
+ directories: [leasedBufsDir],
336
480
  cutoffMs: bufCutoffMs,
337
481
  protectedPaths: currentEvidence.protectedPaths,
338
482
  unknownPaths: currentEvidence.unknownPaths,
@@ -341,12 +485,16 @@ function runGcWithHistorySnapshot(ctx, db, {
341
485
  ...(currentEvidence.gcCutoffs || [])
342
486
  ]
343
487
  });
488
+ assertDirectoryUnchanged();
344
489
  const currentObservedAt = now();
345
- const currentObservation = tx(db, () => observeClockSafetyInTransaction(db, {
346
- operation: 'gc',
347
- gcCutoffs: bufferPlanGcCutoffs(currentPlan, bufferRetentionSec),
348
- nowSec: currentObservedAt
349
- }));
490
+ const currentObservation = tx(db, () => {
491
+ assertDirectoryUnchanged();
492
+ return observeClockSafetyInTransaction(db, {
493
+ operation: 'gc',
494
+ gcCutoffs: bufferPlanGcCutoffs(currentPlan, bufferRetentionSec),
495
+ nowSec: currentObservedAt
496
+ });
497
+ });
350
498
  if (clockGraceSuppressed(currentObservedAt, currentObservation.graceUntil)) {
351
499
  databaseGraceActive = true;
352
500
  return deferBufferPlan(currentPlan);
@@ -360,9 +508,11 @@ function runGcWithHistorySnapshot(ctx, db, {
360
508
  }
361
509
  return applyBufferPlan(currentPlan);
362
510
  });
363
- results.buf_files = bufferResult.deleted;
364
- results.protected_buf_files = bufferResult.protected;
365
- results.deferred_buf_files = bufferResult.deferred;
511
+ if (bufferResult !== null) {
512
+ results.buf_files = bufferResult.deleted;
513
+ results.protected_buf_files = bufferResult.protected;
514
+ results.deferred_buf_files = bufferResult.deferred;
515
+ }
366
516
  }
367
517
 
368
518
  // A clock jump can make every lock look expired at once. Preserve those rows
@@ -589,7 +739,7 @@ async function prepareManualBufferGc(ctx, db, { retentionSec, dryRun }) {
589
739
  phase: 'prepare',
590
740
  retentionSec,
591
741
  dryRun
592
- }, runtime, { timeoutMs: 5000 });
742
+ }, runtime, { timeoutMs: RUNTIME_BUFFER_GC_TIMEOUT_MS });
593
743
  return {
594
744
  mode: 'runtime',
595
745
  runtime,
@@ -668,7 +818,7 @@ async function applyManualBufferGc(ctx, db, prepared, { dryRun, graceActive }) {
668
818
  const applied = await runtimeRequest(ctx, 'POST', '/api/runtime/gc-buffers', {
669
819
  phase: 'apply',
670
820
  token: prepared.token
671
- }, prepared.runtime, { timeoutMs: 5000 });
821
+ }, prepared.runtime, { timeoutMs: RUNTIME_BUFFER_GC_TIMEOUT_MS });
672
822
  const counts = requireBufferGcResult(applied);
673
823
  if (typeof applied?.complete !== 'boolean' || typeof applied?.graceActive !== 'boolean') {
674
824
  throw new CliError('RUNTIME_BAD_RESPONSE', 'Runtime buffer GC returned invalid completion state');