start-command 0.42.0 → 0.44.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 (36) hide show
  1. package/.changeset/README.md +1 -1
  2. package/CHANGELOG.md +26 -0
  3. package/README.md +2 -0
  4. package/package.json +6 -6
  5. package/src/lib/args-parser-queries.js +64 -3
  6. package/src/lib/docker-inspect-batch.js +3 -0
  7. package/src/lib/execution-attach.js +10 -1
  8. package/src/lib/execution-cleanup.js +8 -0
  9. package/src/lib/execution-control.js +14 -3
  10. package/src/lib/execution-pause.js +701 -0
  11. package/src/lib/execution-recovery.js +6 -0
  12. package/src/lib/execution-resume-all.js +14 -0
  13. package/src/lib/execution-resume.js +16 -1
  14. package/src/lib/execution-store.js +5 -1
  15. package/src/lib/formal-ai-client.js +8 -5
  16. package/src/lib/formal-ai-files.js +474 -61
  17. package/src/lib/formal-ai.js +170 -7
  18. package/src/lib/log-sanitizer.js +5 -0
  19. package/src/lib/query-commands.js +33 -1
  20. package/src/lib/session-probe.js +27 -1
  21. package/src/lib/status-formatter.js +13 -3
  22. package/src/lib/status-probe.js +19 -5
  23. package/src/lib/usage.js +8 -1
  24. package/test/args-parser-attach-resume.js +1 -1
  25. package/test/args-parser-control.js +3 -3
  26. package/test/args-parser.js +1 -1
  27. package/test/fixtures/formal-ai/receipts-258.json +27 -9
  28. package/test/fixtures/formal-ai/receipts-261.json +589 -0
  29. package/test/fixtures/formal-ai/replay.cjs +4 -1
  30. package/test/fixtures/pause-golden.cjs +142 -0
  31. package/test/regression-243.js +4 -1
  32. package/test/regression-258-files.js +27 -5
  33. package/test/regression-258.js +22 -9
  34. package/test/regression-261.js +856 -0
  35. package/test/regression-263.js +867 -0
  36. package/test/shared-golden.js +28 -3
@@ -34,6 +34,7 @@ const ResumeAllAction = {
34
34
  RUNNING: 'running',
35
35
  RECONCILED: 'reconciled',
36
36
  UNKNOWN: 'unknown',
37
+ PAUSED: 'paused',
37
38
  };
38
39
 
39
40
  function formatResumeAllAsLinksNotation(summary) {
@@ -137,6 +138,19 @@ function resumeAllExecutions(store, deps = {}) {
137
138
  continue;
138
139
  }
139
140
 
141
+ if (probe.state === SessionState.PAUSED) {
142
+ // Neither restarted nor re-watched while frozen (issue #263).
143
+ executions.push(
144
+ describeRecord(
145
+ record,
146
+ probe,
147
+ ResumeAllAction.PAUSED,
148
+ `Skipped paused container: ${sessionName}. Use \`$ --unpause ${record.uuid}\` to continue it.`
149
+ )
150
+ );
151
+ continue;
152
+ }
153
+
140
154
  if (probe.alive && probe.backend === 'docker') {
141
155
  startWatcher(
142
156
  sessionName,
@@ -43,7 +43,11 @@ const {
43
43
  rollbackLaunch,
44
44
  failLaunchedPersistence,
45
45
  } = require('./launch-persistence');
46
- const { SessionState, probeSession } = require('./session-probe');
46
+ const {
47
+ SessionState,
48
+ pausedSessionError,
49
+ probeSession,
50
+ } = require('./session-probe');
47
51
  const { getDockerNetworks } = require('./docker-network-lifecycle');
48
52
  const {
49
53
  writeCommandHandoff,
@@ -217,6 +221,10 @@ function buildResumePlan(
217
221
  return { error: 'Only detached isolated executions can be resumed.' };
218
222
  }
219
223
 
224
+ if (probe.state === SessionState.PAUSED) {
225
+ return pausedSessionError('resume', record.uuid, sessionName);
226
+ }
227
+
220
228
  if (probe.alive) {
221
229
  return {
222
230
  error:
@@ -481,6 +489,13 @@ async function resumeExecution(store, identifier, deps = {}) {
481
489
  const runner = deps.runner || runCommand;
482
490
  const probeFn = deps.probe || ((r) => probeSession(r, runner));
483
491
  const probe = probeFn(record);
492
+ if (probe.state === SessionState.PAUSED) {
493
+ // Refused before any snapshot or resource change (issue #263).
494
+ return {
495
+ success: false,
496
+ ...pausedSessionError('resume', record.uuid, probe.sessionName),
497
+ };
498
+ }
484
499
  const opts = record.options || {};
485
500
  let resources;
486
501
  try {
@@ -663,7 +663,11 @@ class ExecutionStore {
663
663
  const staleRecords = [];
664
664
 
665
665
  for (const record of executingRecords) {
666
- if (require('./launch-owner').hasActiveLaunch(record)) {
666
+ if (
667
+ require('./launch-owner').hasActiveLaunch(record) ||
668
+ require('./execution-pause').isConfirmedPaused(record)
669
+ ) {
670
+ // A paused container is alive, just frozen (issue #263).
667
671
  continue;
668
672
  }
669
673
  let isStale = false;
@@ -15,6 +15,7 @@ const { API_PREFIX } = require('./formal-ai-runtime');
15
15
  const {
16
16
  FILE_TOOLS,
17
17
  describeFileCall,
18
+ echoedByTool,
18
19
  normalizeFileArgs,
19
20
  } = require('./formal-ai-files');
20
21
 
@@ -144,7 +145,12 @@ const GAP_SCHEMAS = Object.freeze({
144
145
  'EditToolUnavailable',
145
146
  'ReadToolUnavailable',
146
147
  ],
147
- 'workspace-change-verification-gap/v1': ['VerificationRunToolUnavailable'],
148
+ // SourcePreimageUnavailable: Formal AI could not bind the complete Read
149
+ // before its edit to the change (issue #261).
150
+ 'workspace-change-verification-gap/v1': [
151
+ 'VerificationRunToolUnavailable',
152
+ 'SourcePreimageUnavailable',
153
+ ],
148
154
  });
149
155
  const CAPABILITIES = new Set([
150
156
  'read',
@@ -196,10 +202,7 @@ function recognizeCapabilityGap(content, messages = []) {
196
202
  ) {
197
203
  return null;
198
204
  }
199
- const echoed = messages.some(
200
- (m) => m && m.role === 'tool' && String(m.content || '').includes(text)
201
- );
202
- if (echoed) {
205
+ if (echoedByTool(text, messages)) {
203
206
  return null;
204
207
  }
205
208
  return {