@corvio/cli 0.1.0-beta.35 → 0.1.0-beta.37

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/README.md CHANGED
@@ -12,9 +12,9 @@ corvio collaboration status --json --no-input
12
12
  corvio ask --prompt 'Summarize recurring launch risks' --json
13
13
  corvio files upload --file ./research.pdf --json
14
14
  corvio files get <asset_id> --content --json
15
- corvio files organize <asset_id> --instruction 'Create a reusable decision brief' --yes --json
16
- corvio files operation <operation_id> --json
17
- corvio files resume <operation_id> --yes --json
15
+ corvio files organize <asset_id> --instruction 'Create a reusable decision brief' --yes --wait-until-terminal --timeout-seconds 1200 --json
16
+ corvio files operation <operation_id> --wait-until-terminal --timeout-seconds 1200 --json
17
+ corvio files resume <operation_id> --yes --wait-until-terminal --timeout-seconds 1200 --json
18
18
  corvio docs read <workspace_id/document_id> --mode auto --json
19
19
  corvio docs patch <workspace_id/document_id> --input ./patch.json --operation-id <id> --yes --json
20
20
  corvio docs table-read <workspace_id/document_id> --limit 25 --json
@@ -38,8 +38,19 @@ require reauthorization.
38
38
 
39
39
  The CLI rejects unknown/duplicate options and malformed integer bounds before network access. Normal API calls have a bounded timeout (`CORVIO_REQUEST_TIMEOUT_MS`, maximum ten minutes); only reads, explicit idempotency contracts, revision guards, and other owner-declared safe operations retry automatically. Document commands accept both bare document IDs and canonical `workspace_id/document_id` handles; a canonical handle that disagrees with the selected Workspace fails before remote access, and successful reads return `canonical_id` for direct reuse. `docs read --mode auto` returns a complete small Page or a large-Page overview, then supports exact section, line-range, literal-search, and full projections. `docs patch` applies 1–20 exact non-overlapping L# replacements at the fetched revision and returns a compact readback instead of replaying untouched content. `docs get --output` keeps compatibility full-body download but omits the body from stdout; create/update request compact mutation receipts. Document updates preflight only a bounded overview for the current revision when `--expected-revision` is omitted. `docs table-read` returns a bounded stable-ID projection; `docs table-mutate` accepts a JSON payload for at most 50 cell updates or row appends and requires the fetched revision, while formulas, styles, structure, sorting, and semantic transformations remain `corvio ask --allow-actions` work. When the executing principal is a connected Agent, document and table mutations also require `--change-summary`, create a visible document-level comment after the guarded update, and return its receipt; a comment failure is reported as a partial effect rather than silent success. When a Corvio document or comment supplied the task, use `corvio agent closeout` to return the verified outcome, optionally resolve the thread, and read both document and thread back. Downloads, uploads, and Markdown pulls verify SHA-256 receipts before replacing local files; local writes are atomic, remote default filenames cannot escape the current directory, and Sync refuses symbolic-link traversal while checkpointing each successful push.
40
40
 
41
- For `corvio ask`, pass the user's natural goal, complete decision-relevant context, and explicit constraints. Unless the user chose them,
42
- do not invent taxonomy, titles, artifact counts, or Corvio's internal plan. Read the terminal Question receipt and use `artifact.url` or
41
+ For `corvio ask`, keep the user's natural question unchanged, then append the smallest complete decision-relevant context or stable handles
42
+ as a separate clause. A carried Project handle normally bounds a cross-branch question without enumerating all descendants. Handles are
43
+ evidence addresses: do not invent a comparison rubric, prescribe reasoning steps, substitute another user objective, or turn current Tree
44
+ labels into a detailed task plan. Unless the user chose them, do not invent taxonomy, titles, artifact counts, or Corvio's internal plan.
45
+ In a bounded read-only continuation, use `corvio ask --workspace <workspace_id> --prompt "<unchanged question; Context: project handle>"
46
+ --json --no-input` when a trusted current-task or prior receipt already
47
+ proves this authenticated CLI and exact Workspace. Answer-only is
48
+ the default: omit `--allow-actions`; the CLI has no `--mode` option. Start the command once; do not launch an identical Question concurrently
49
+ or retry before terminal exit. The command returns the terminal Question without model-visible
50
+ polling. Transport continuity does not prove a cheaper processing profile; leave profile selection on `auto` unless the unresolved semantic
51
+ bottleneck independently justifies another profile. Do not install, probe, or switch to the CLI only for that optimization, and do not
52
+ extend it to an unbound Workspace.
53
+ Read the terminal Question receipt and use `artifact.url` or
43
54
  `links.primary_artifact` verbatim; `reader_output` artifacts are deliverables, `structure_container` artifacts are hierarchy, and
44
55
  `node_id` is never a document URL.
45
56
 
@@ -47,7 +58,7 @@ Treat files as a value ladder, not one upload event. Upload/finalize preserves t
47
58
 
48
59
  Use `corvio files get <asset_id> --content` when the task needs only bounded facts from one retained source. The response preserves the immutable original as authority, reports its source hash and whether the projection is complete or truncated, and does not create a Question or materialize another document. Never infer unseen workbook rows/sheets or document sections.
49
60
 
50
- File organization is asynchronous. A queued organization receipt is progress, not completion: continue from its stable operation ID with `corvio files operation <operation_id>`. Blocked operations expose their typed error and can be continued with `corvio files resume <operation_id> --yes`; queued/running/blocked operations can be stopped with `corvio files cancel <operation_id> --yes`. Report completion only after the terminal receipt exposes the output document, source reconciliation, Skill evaluation, and durable links. `skills_extraction_mode=always` requires the evaluation; `evaluated_no_qualifying_skill` is a valid terminal result and is preferable to a fabricated Skill.
61
+ File organization is asynchronous. A queued organization receipt is progress, not completion. When this authenticated CLI is already the natural transport, `--wait-until-terminal` keeps deterministic status reads inside one foreground process; `--timeout-seconds` is a bounded local deadline, and expiry returns the latest non-terminal receipt with `transport_wait.status=deadline_reached`. Without that flag, `corvio files operation <operation_id>` remains one immediate read. Blocked operations expose their typed error and can be continued with `corvio files resume <operation_id> --yes`; queued/running/blocked operations can be stopped with `corvio files cancel <operation_id> --yes`. Report completion only after the terminal receipt exposes the output document, source reconciliation, Skill evaluation, and durable links. `skills_extraction_mode=always` requires the evaluation; `evaluated_no_qualifying_skill` is a valid terminal result and is preferable to a fabricated Skill.
51
62
 
52
63
  ## Project Agent collaboration
53
64
 
package/dist/cli.js CHANGED
@@ -39,13 +39,17 @@ import {
39
39
  agentCredentialPath,
40
40
  } from "./core.js";
41
41
 
42
- const VERSION = "0.1.0-beta.35";
42
+ const VERSION = "0.1.0-beta.37";
43
43
  const DEFAULT_API_BASE = "https://api.corvio.ai/v1";
44
44
  const DEFAULT_APP_BASE = "https://corvio.ai";
45
45
  const USER_AGENT = `corvio-cli/${VERSION}`;
46
46
  const MAX_UPLOAD_BYTES = 100 * 1024 * 1024;
47
47
  const MAX_SYNC_FILES = 2_000;
48
48
  const MAX_MARKDOWN_CHARACTERS = 120_000;
49
+ const DEFAULT_FILE_OPERATION_WAIT_TIMEOUT_SECONDS = 1_200;
50
+ const MAX_FILE_OPERATION_WAIT_TIMEOUT_SECONDS = 7_200;
51
+ const FILE_OPERATION_PENDING_STATUSES = new Set(["accepted", "pending", "prepared", "queued", "running"]);
52
+ const READ_ONLY_LOCAL_STATE_ERROR_CODES = new Set(["EACCES", "EPERM", "EROFS"]);
49
53
  const CORVIO_SKILL_ARCHIVE = "https://corvio.ai/developers/skills/corvio-operate-workspace/corvio-operate-workspace.zip";
50
54
  const AGENT_SKILLS_INSTALLER_VERSION = "1.5.23";
51
55
  const GLOBAL_OPTIONS = Object.freeze([
@@ -60,6 +64,17 @@ const GLOBAL_OPTIONS = Object.freeze([
60
64
  "version",
61
65
  ]);
62
66
 
67
+ function isReadOnlyLocalStateError(error) {
68
+ const visited = new Set();
69
+ let current = error;
70
+ while (current && typeof current === "object" && !visited.has(current)) {
71
+ visited.add(current);
72
+ if (READ_ONLY_LOCAL_STATE_ERROR_CODES.has(String(current.code || "").toUpperCase())) return true;
73
+ current = current.cause;
74
+ }
75
+ return false;
76
+ }
77
+
63
78
  const COMMAND_OPTIONS = Object.freeze({
64
79
  "auth:login": ["force"],
65
80
  "auth:status": [],
@@ -126,13 +141,15 @@ const COMMAND_OPTIONS = Object.freeze({
126
141
  "files:upload": [
127
142
  "file", "mime-type", "organize", "instruction", "target-root-node-id", "additional-asset-ids",
128
143
  "processing-profile", "max-processing-profile", "scan-mode", "skills-extraction-mode", "caller-model",
144
+ "wait-until-terminal", "timeout-seconds",
129
145
  ],
130
146
  "files:organize": [
131
147
  "id", "additional-asset-ids", "instruction", "target-root-node-id",
132
148
  "processing-profile", "max-processing-profile", "scan-mode", "skills-extraction-mode", "caller-model",
149
+ "wait-until-terminal", "timeout-seconds",
133
150
  ],
134
- "files:operation": ["id"],
135
- "files:resume": ["id"],
151
+ "files:operation": ["id", "wait-until-terminal", "timeout-seconds"],
152
+ "files:resume": ["id", "wait-until-terminal", "timeout-seconds"],
136
153
  "files:cancel": ["id"],
137
154
  "files:open": ["id"],
138
155
  "sync:init": ["root-node-id", "dir", "name", "force"],
@@ -180,7 +197,7 @@ const COMMAND_HELP = Object.freeze({
180
197
  workspaces: `Usage:\n corvio workspaces list\n corvio workspaces use <workspace-id>\n corvio workspaces current\n corvio workspaces create --name <name> [--kind personal|team]\n\nWorkspace status and blocked capability reasons are returned with each item.`,
181
198
  capabilities: `Usage: corvio capabilities [--json]\n\nShow live API resources, principal scopes, selected Workspace status, and CLI compatibility.`,
182
199
  collaboration: `Usage:\n corvio collaboration status [--project <path>] [--provider codex|claude_code|copilot|workbuddy]\n\nCheck local Corvio prerequisites and every discoverable Corvio Skill copy. Current copies stay quiet; non-current receipts include one compact Agent notice and a source-aware next action. Standalone refresh commands pin the verified installer, select exactly one provider, and preserve project/global scope. WorkBuddy inspection is read-only and never changes ~/.workbuddy. The CLI cannot prove that the current host task loaded a particular copy, or whether remote MCP or host memory is active.`,
183
- ask: `Usage: corvio ask --prompt <text> [--file <path>] [--asset-ids <id,id>] [--conversation-id <id>] [--sources workspace,web,memory] [--allow-actions] [--processing-profile auto|economy|standard|deep] [--max-processing-profile economy|standard|deep] [--scan-mode auto|always|off] [--skills-extraction-mode auto|always|off] [--caller-model <host-model>] [--context-file <json>] [--idempotency-key <key>]\n\nPass the user's natural goal, complete decision-relevant context, and explicit constraints. Unless the user chose them, do not invent taxonomy, titles, artifact count, or Corvio's internal plan; Corvio chooses the cognitive structure and carriers. --file retains the selected local file, verifies its hash, and attaches the finalized Asset to this exact question. --asset-ids attaches already-retained Corvio Assets. Use --allow-actions when Corvio should create the Page, Spreadsheet, Presentation, Code, HTML Artifact, Memory, or qualifying Project Skill that best serves the result. A continuation handle is subject evidence, not necessarily the durable write destination: for a reusable pattern or method, resolve the fitting same-scope Memory or Skill owner before admitting or merging it. Read the terminal Question receipt and use artifact.url or links.primary_artifact verbatim; reader_output artifacts are deliverables, structure_container artifacts are hierarchy, and node_id is never a document URL. Upload alone is not evidence of interpretation. Skill extraction mode always requires --allow-actions and an evidence-backed decision, not forced Skill creation; answer-only questions may use auto or off.`,
200
+ ask: `Usage: corvio ask --prompt <text> [--file <path>] [--asset-ids <id,id>] [--conversation-id <id>] [--sources workspace,web,memory] [--allow-actions] [--processing-profile auto|economy|standard|deep] [--max-processing-profile economy|standard|deep] [--scan-mode auto|always|off] [--skills-extraction-mode auto|always|off] [--caller-model <host-model>] [--context-file <json>] [--idempotency-key <key>]\n\nAnswer-only is the default: omit --allow-actions; there is no --mode option. Keep the user's natural question unchanged, then append the smallest complete decision-relevant context or stable handles as a separate clause. A carried Project handle normally bounds a cross-branch question without enumerating all descendants. Handles are evidence addresses: do not invent a comparison rubric, prescribe reasoning steps, substitute another user objective, or turn current Tree labels into a detailed task plan. Unless the user chose them, do not invent taxonomy, titles, artifact count, or Corvio's internal plan; Corvio chooses the cognitive structure and carriers. In a bounded read-only continuation, start this foreground command exactly once when a trusted current-task or prior receipt already proves this authenticated CLI and exact Workspace; do not launch an identical Question concurrently or retry before terminal exit. If the host shell yields a running-session handle, wait on that exact handle until the CLI exits instead of answering from prior context. It returns the terminal Question without model-visible polling. Transport continuity does not prove a cheaper processing profile; leave processing on auto unless the remaining transformation independently justifies another profile. Do not install, probe, or switch to the CLI only for that optimization, and do not extend it to an unbound Workspace. --file retains the selected local file, verifies its hash, and attaches the finalized Asset to this exact question. --asset-ids attaches already-retained Corvio Assets. Use --allow-actions when Corvio should create the Page, Spreadsheet, Presentation, Code, HTML Artifact, Memory, or qualifying Project Skill that best serves the result. A continuation handle is subject evidence, not necessarily the durable write destination: for a reusable pattern or method, resolve the fitting same-scope Memory or Skill owner before admitting or merging it. Read the terminal Question receipt and use artifact.url or links.primary_artifact verbatim; reader_output artifacts are deliverables, structure_container artifacts are hierarchy, and node_id is never a document URL. Upload alone is not evidence of interpretation. Skill extraction mode always requires --allow-actions and an evidence-backed decision, not forced Skill creation; answer-only questions may use auto or off.`,
184
201
  agents: `Usage:\n corvio agents list\n corvio agents connect [--provider codex|claude_code|copilot] [--project <path>] [--permission-profile read_only|workspace_write|danger_full_access] [--trigger-policy owner_only|workspace_members] [--display-name <name>] [--recover-credential] [--no-listen]\n corvio agents create --handle <handle> --display-name <name> [--provider codex|claude_code|copilot|custom] [--permissions read,comment,edit]\n corvio agents update <id> [--display-name <name>] [--status active|paused|revoked] [--permissions <list>]\n corvio agents rotate <id>\n\n'connect' binds the current Git project (or --project path) to one stable personal Agent. Repeating it is idempotent; another project creates another Agent. It reports provider/CLI, listener, and local Skill prerequisites separately; run corvio collaboration status after installing or refreshing the Skill. The CLI cannot prove that the current host task loaded those surfaces. The default workspace_write profile is limited by the provider's native sandbox. owner_only is the safe trigger default. Secrets and absolute project paths stay local.`,
185
202
  agent: `Usage:\n corvio agent claim [--worker-id <id>] [--wait-seconds <0-30>]\n corvio agent renew <event-id> --lease-token <token> [--lease-seconds <30-900>]\n corvio agent complete <event-id> --lease-token <token> [--result-file <json>]\n corvio agent fail <event-id> --lease-token <token> --error <message>\n corvio agent mentions --document-id <id> [--query <name>]\n corvio agent comments --document-id <id>\n corvio agent comment --document-id <id> --body <text> [--selection-file <json>] [--start-new-chain] [--idempotency-key <key>]\n corvio agent reply <thread-id> --document-id <id> --body <text> [--source-event-id <id> --source-event-lease-token <token>|--start-new-chain] [--idempotency-key <key>]\n corvio agent status <thread-id> --document-id <id> --status open|resolved\n corvio agent closeout --document-id <id> [--thread-id <id>] --body <verified-result> [--status open|resolved] [--idempotency-key <key>]\n corvio agent run [--provider codex|claude_code|copilot|custom] [--handler <executable>] [--agent <id>] [--once] [--processing-profile auto|economy|standard|deep] [--max-processing-profile economy|standard|deep] [--scan-mode auto|always|off] [--skills-extraction-mode auto|always|off] [--caller-model <host-model>]\n\nUse mentions before commenting when the target is uncertain; aliases resolve to stable user or Agent IDs and ambiguity fails closed. Use closeout when a Corvio document or comment owned the task: it returns the verified result to that surface and reads document/comments back. Agent-to-Agent comments require either a claimed source event or explicit --start-new-chain. The runner iteratively exchanges origin-scoped evidence with Corvio Query/Writer, then invokes the bound provider inside its local project. Local code changes use the recorded provider permission profile; complex Corvio document changes belong to Query/Writer. Provider children never receive Corvio credentials.`,
186
203
  questions: `Usage:\n corvio questions list [--limit <n>] [--cursor <cursor>] [--conversation-id <id>]\n corvio questions get <question-id>`,
@@ -188,7 +205,7 @@ const COMMAND_HELP = Object.freeze({
188
205
  search: `Usage: corvio search <query> [--limit <n>] [--page-id <id>] [--node-id <id>]`,
189
206
  docs: `Usage:\n corvio docs list [--limit <n>] [--cursor <cursor>] [--lifecycle active|archived]\n corvio docs get <id> [--output <path>]\n corvio docs read <id> [--mode auto|overview|line-range|section|search|full] [--start-line <n> --end-line <n>|--section-ref <S#>|--query <text>] [--max-chars <n>]\n corvio docs patch <id> --input <json> --yes [--operation-id <id>] [--change-summary <text>]\n corvio docs table-read <id> [--table-id <id>] [--after-row-id <id>] [--limit <1-100>]\n corvio docs table-mutate <id> --input <json> --yes [--operation-id <id>] [--change-summary <text>]\n corvio docs create --title <title> [--file <path>|--markdown <text>] [--parent-node-id <id>] [--idempotency-key <key>]\n corvio docs update <id> [--title <title>] [--file <path>|--markdown <text>] [--expected-revision <n>] [--change-summary <text>]\n corvio docs move <id> --parent-node-id <id>\n corvio docs archive <id> --yes\n corvio docs restore <id> --yes\n corvio docs share <id> --yes [--display-name <name>]\n corvio docs open <id>\n\nUse \`docs read --mode auto\` first for a small full body or a large-document overview, then read an exact section or line range. Use \`docs patch\` when the host already knows 1-20 exact L# replacements; it preserves untouched lines and returns a compact verified delta. A selected Markdown file can become an editable Page without resending the body through the model. Use \`docs update\` only when whole-body replacement is the smallest faithful change. Delegate document-wide judgment, cross-source synthesis, structure/formatting interpretation, and typed Spreadsheet, Presentation, Code, or HTML work to \`corvio ask --allow-actions\`. A Project is a structure container; body writes fail with project_structure_container_body_write_disabled, while a title-only Project rename remains valid. Projects stay at the Workspace root and cannot be nested; use \`files organize\` or \`ask --allow-actions\` for Work Model maintenance. Use table-read plus table-mutate for bounded stable-ID cell updates or row appends. Agent-authenticated document changes require --change-summary and create a visible document-level comment after the guarded update. When a document/comment is the task owner, use \`corvio agent closeout\` to return verified work to that same collaboration surface and read it back.`,
190
207
  projects: `Usage:\n corvio projects list [--limit <n>]\n corvio projects create --title <title> [--idempotency-key <key>]\n\nProjects are durable root-level grouping owners in the Corvio Docs tree and cannot be nested under another Project. Reuse a matching Project instead of creating one ceremonial Project per file; use semantic branch Pages and leaves for deeper Work Models. 'corvio folders' is an alias.`,
191
- files: `Usage:\n corvio files list [--limit <n>]\n corvio files get <id> [--content]\n corvio files download <id> [--output <path>]\n corvio files upload --file <path> [--mime-type <type>] [--organize --instruction <text> --additional-asset-ids <id,id> --yes]\n corvio files organize <id> --instruction <text> --yes [--additional-asset-ids <id,id>] [--target-root-node-id <project-node-id>] [--processing-profile auto|economy|standard|deep] [--scan-mode auto|always|off] [--skills-extraction-mode auto|always|off]\n corvio files operation <operation-id>\n corvio files resume <operation-id> --yes\n corvio files cancel <operation-id> --yes\n corvio files open <id>\n\nUse --content only when source facts are needed. It returns one bounded AI-safe projection with hash, completeness, and truncation receipts; it does not rewrite or materialize the original. Upload alone preserves the exact original, provenance, hash, policy, and stable Asset ID. Organization is a separate asynchronous value step: one coherent source set can produce reader-facing structure, source reconciliation, Memory candidates, and qualifying Project Skills. Pass --instruction a weak natural goal plus explicit user constraints and only authority facts needed to prevent wrong identity or scope. Keep source-derived facts in the source; do not precompute an outline, edit checklist, taxonomy, titles, artifact count, or sole leaf target from current visibility. A heterogeneous source may update several real Projects while its provenance carrier stays a standalone document; do not direct Corvio to make the source Page an umbrella Project. For a newly finalized Asset that should enter a Work Model or Skill evaluation, use one organize operation as the sole semantic owner; do not open ask before or after it for the same Asset set. --target-root-node-id is optional and accepts only a Project node_id returned by projects list, never a Page/document UUID or leaf node; omit it when the Project is not known. Use 'files operation' to reach terminal and inspect output_document plus skills_evaluation; always requires evaluation but may correctly return evaluated_no_qualifying_skill. Resume reuses the same durable operation after resolving its blocker; cancel is idempotent.`,
208
+ files: `Usage:\n corvio files list [--limit <n>]\n corvio files get <id> [--content]\n corvio files download <id> [--output <path>]\n corvio files upload --file <path> [--mime-type <type>] [--organize --instruction <text> --additional-asset-ids <id,id> --yes] [--wait-until-terminal] [--timeout-seconds <1-7200>]\n corvio files organize <id> --instruction <text> --yes [--additional-asset-ids <id,id>] [--target-root-node-id <project-node-id>] [--processing-profile auto|economy|standard|deep] [--scan-mode auto|always|off] [--skills-extraction-mode auto|always|off] [--wait-until-terminal] [--timeout-seconds <1-7200>]\n corvio files operation <operation-id> [--wait-until-terminal] [--timeout-seconds <1-7200>]\n corvio files resume <operation-id> --yes [--wait-until-terminal] [--timeout-seconds <1-7200>]\n corvio files cancel <operation-id> --yes\n corvio files open <id>\n\nUse --content only when source facts are needed. It returns one bounded AI-safe projection with hash, completeness, and truncation receipts; it does not rewrite or materialize the original. Upload alone preserves the exact original, provenance, hash, policy, and stable Asset ID. Organization is a separate asynchronous value step: one coherent source set can produce reader-facing structure, source reconciliation, Memory candidates, and qualifying Project Skills. Pass --instruction a weak natural goal plus explicit user constraints and only authority facts needed to prevent wrong identity or scope. Keep source-derived facts in the source; do not precompute an outline, edit checklist, taxonomy, titles, artifact count, or sole leaf target from current visibility. A heterogeneous source may update several real Projects while its provenance carrier stays a standalone document; do not direct Corvio to make the source Page an umbrella Project. For a newly finalized Asset that should enter a Work Model or Skill evaluation, use one organize operation as the sole semantic owner; do not open ask before or after it for the same Asset set. --target-root-node-id is optional and accepts only a Project node_id returned by projects list, never a Page/document UUID or leaf node; omit it when the Project is not known. Use --wait-until-terminal when a filesystem-capable Host should keep deterministic polling inside this one foreground CLI invocation; --timeout-seconds is a local wait deadline, and reaching it returns the latest non-terminal receipt rather than claiming completion. Without that flag, 'files operation' remains an immediate read. Inspect terminal output_document plus skills_evaluation; evaluated_no_qualifying_skill is valid. Resume reuses the same durable operation after resolving its blocker; cancel is idempotent.`,
192
209
  sync: `Usage:\n corvio sync init --root-node-id <id> [--dir <path>] [--name <name>]\n corvio sync status|plan|pull [--dir <path>]\n corvio sync push --yes [--dir <path>]\n corvio sync resolve --conflict-id <id> --strategy use-remote|keep-local --yes [--dir <path>]`,
193
210
  update: `Usage: corvio update check [--json]\n\nCompare this executable with the reviewed API policy and the npm beta dist-tag.`,
194
211
  });
@@ -1295,18 +1312,31 @@ async function ask(options, positionals) {
1295
1312
  throw error;
1296
1313
  }
1297
1314
  const conversationId = String(result?.conversation_id || result?.data?.conversation_id || "").trim();
1315
+ let localContinuity = null;
1298
1316
  if (conversationId && execution.selection?.workspaceId) {
1299
- await storeConversationRoutingReceipt({
1300
- conversationId,
1301
- workspaceId: execution.selection.workspaceId,
1302
- agentId: execution.credential?.metadata?.agent_id || null,
1303
- apiBase: baseUrl(options),
1304
- });
1317
+ try {
1318
+ await storeConversationRoutingReceipt({
1319
+ conversationId,
1320
+ workspaceId: execution.selection.workspaceId,
1321
+ agentId: execution.credential?.metadata?.agent_id || null,
1322
+ apiBase: baseUrl(options),
1323
+ });
1324
+ } catch (error) {
1325
+ if (!isReadOnlyLocalStateError(error)) throw error;
1326
+ localContinuity = {
1327
+ status: "not_persisted",
1328
+ reason: "local_config_read_only",
1329
+ error_code: String(error.code || "").toUpperCase(),
1330
+ conversation_id: conversationId,
1331
+ workspace_id: execution.selection.workspaceId,
1332
+ };
1333
+ }
1305
1334
  }
1306
1335
  writeResult(
1307
1336
  {
1308
1337
  ...result,
1309
1338
  ...(attachedAssets.length > 0 ? { attached_assets: attachedAssets } : {}),
1339
+ ...(localContinuity ? { local_continuity: localContinuity } : {}),
1310
1340
  routing: execution.routing,
1311
1341
  },
1312
1342
  { json: options.json },
@@ -3456,7 +3486,77 @@ async function uploadWorkspaceFile(api, inputPath, mimeTypeOverride) {
3456
3486
  return result;
3457
3487
  }
3458
3488
 
3489
+ function fileOperationWaitTimeoutSeconds(options) {
3490
+ if (!options["wait-until-terminal"]) {
3491
+ if (options["timeout-seconds"] !== undefined) {
3492
+ throw new CliError("--timeout-seconds requires --wait-until-terminal.", {
3493
+ code: "file_operation_wait_required",
3494
+ });
3495
+ }
3496
+ return null;
3497
+ }
3498
+ return numberOption(
3499
+ options,
3500
+ "timeout-seconds",
3501
+ DEFAULT_FILE_OPERATION_WAIT_TIMEOUT_SECONDS,
3502
+ { minimum: 1, maximum: MAX_FILE_OPERATION_WAIT_TIMEOUT_SECONDS },
3503
+ );
3504
+ }
3505
+
3506
+ function fileOperationIsPending(result) {
3507
+ return FILE_OPERATION_PENDING_STATUSES.has(String(result?.status || "").trim().toLowerCase());
3508
+ }
3509
+
3510
+ function fileOperationDeadlineReceipt(result, { timeoutSeconds, startedAtMs, operationReadCount }) {
3511
+ return {
3512
+ ...result,
3513
+ transport_wait: {
3514
+ status: "deadline_reached",
3515
+ terminal: false,
3516
+ timeout_seconds: timeoutSeconds,
3517
+ elapsed_seconds: Math.max(0, Math.round((Date.now() - startedAtMs) / 1000)),
3518
+ operation_read_count: operationReadCount,
3519
+ continuation: "Run the same files operation command with --wait-until-terminal; do not start a replacement operation.",
3520
+ },
3521
+ };
3522
+ }
3523
+
3524
+ async function waitForFileOperation(api, operationId, { initialResult = null, timeoutSeconds }) {
3525
+ const startedAtMs = Date.now();
3526
+ const deadlineAtMs = startedAtMs + timeoutSeconds * 1000;
3527
+ let result = initialResult;
3528
+ let operationReadCount = 0;
3529
+ while (true) {
3530
+ if (!result) {
3531
+ result = await api.request(`/file-operations/${resourcePath(operationId)}`);
3532
+ operationReadCount += 1;
3533
+ }
3534
+ if (!fileOperationIsPending(result)) return result;
3535
+
3536
+ const remainingMs = deadlineAtMs - Date.now();
3537
+ if (remainingMs <= 0) {
3538
+ return fileOperationDeadlineReceipt(result, {
3539
+ timeoutSeconds,
3540
+ startedAtMs,
3541
+ operationReadCount,
3542
+ });
3543
+ }
3544
+ const retryAfterSeconds = Number(result.retry_after_seconds);
3545
+ const requestedDelayMs = Number.isFinite(retryAfterSeconds) && retryAfterSeconds >= 0
3546
+ ? retryAfterSeconds * 1000
3547
+ : 5_000;
3548
+ await sleep(Math.min(remainingMs, Math.max(50, Math.min(30_000, requestedDelayMs))));
3549
+ result = null;
3550
+ }
3551
+ }
3552
+
3459
3553
  async function files(options, action, id) {
3554
+ const waitTimeoutSeconds = fileOperationWaitTimeoutSeconds(options);
3555
+ if (action === "upload" && waitTimeoutSeconds !== null && !options.organize) {
3556
+ throw new CliError("--wait-until-terminal requires --organize for files upload.", {
3557
+ code: "file_operation_wait_requires_organize",
3558
+ });
3559
+ }
3460
3560
  const { api } = await executionClient(options);
3461
3561
  const assetId = id || options.id;
3462
3562
  let result;
@@ -3492,27 +3592,50 @@ async function files(options, action, id) {
3492
3592
  headers: options["caller-model"] ? { "x-corvio-caller-model": String(options["caller-model"]) } : {},
3493
3593
  body: fileOrganizationBody(options),
3494
3594
  });
3495
- result = { file: result, organization };
3595
+ result = {
3596
+ file: result,
3597
+ organization: waitTimeoutSeconds === null
3598
+ ? organization
3599
+ : await waitForFileOperation(api, organization.operation_id, {
3600
+ initialResult: organization,
3601
+ timeoutSeconds: waitTimeoutSeconds,
3602
+ }),
3603
+ };
3496
3604
  }
3497
3605
  } else if (action === "organize") {
3498
3606
  requireConfirmation(options, "Starting a file organization run");
3499
- result = await api.request(`/files/${resourcePath(assetId || option(options, "id", { required: true }))}:organize`, {
3607
+ const organization = await api.request(`/files/${resourcePath(assetId || option(options, "id", { required: true }))}:organize`, {
3500
3608
  method: "POST",
3501
3609
  headers: options["caller-model"] ? { "x-corvio-caller-model": String(options["caller-model"]) } : {},
3502
3610
  body: fileOrganizationBody(options),
3503
3611
  });
3612
+ result = waitTimeoutSeconds === null
3613
+ ? organization
3614
+ : await waitForFileOperation(api, organization.operation_id, {
3615
+ initialResult: organization,
3616
+ timeoutSeconds: waitTimeoutSeconds,
3617
+ });
3504
3618
  } else if (action === "operation") {
3505
- result = await api.request(`/file-operations/${resourcePath(assetId || option(options, "id", { required: true }))}`);
3619
+ const operationId = assetId || option(options, "id", { required: true });
3620
+ result = waitTimeoutSeconds === null
3621
+ ? await api.request(`/file-operations/${resourcePath(operationId)}`)
3622
+ : await waitForFileOperation(api, operationId, { timeoutSeconds: waitTimeoutSeconds });
3506
3623
  } else if (action === "resume" || action === "cancel") {
3507
3624
  const operationId = assetId || option(options, "id", { required: true });
3508
3625
  requireConfirmation(
3509
3626
  options,
3510
3627
  `${action === "resume" ? "Resuming" : "Cancelling"} this file organization operation`,
3511
3628
  );
3512
- result = await api.request(`/file-operations/${resourcePath(operationId)}:${action}`, {
3629
+ const operation = await api.request(`/file-operations/${resourcePath(operationId)}:${action}`, {
3513
3630
  method: "POST",
3514
3631
  retrySafe: true,
3515
3632
  });
3633
+ result = action === "resume" && waitTimeoutSeconds !== null
3634
+ ? await waitForFileOperation(api, operationId, {
3635
+ initialResult: operation,
3636
+ timeoutSeconds: waitTimeoutSeconds,
3637
+ })
3638
+ : operation;
3516
3639
  } else if (action === "open") {
3517
3640
  const targetId = assetId || option(options, "id", { required: true });
3518
3641
  const target = `${String(process.env.CORVIO_APP_BASE || DEFAULT_APP_BASE).replace(/\/+$/, "")}/app/a/${resourcePath(targetId)}`;
package/dist/core.js CHANGED
@@ -102,6 +102,7 @@ export function parseArgv(argv) {
102
102
  "recover-credential",
103
103
  "no-listen",
104
104
  "start-new-chain",
105
+ "wait-until-terminal",
105
106
  "help",
106
107
  "version",
107
108
  ]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@corvio/cli",
3
- "version": "0.1.0-beta.35",
3
+ "version": "0.1.0-beta.37",
4
4
  "description": "Official Corvio Workspace CLI for questions, knowledge search, documents, and Agent workflows.",
5
5
  "type": "module",
6
6
  "bin": {