@corvio/cli 0.1.0-beta.14 → 0.1.0-beta.16

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
@@ -7,15 +7,20 @@ npm install --global @corvio/cli@beta
7
7
  export CORVIO_API_KEY='cvu_...'
8
8
  corvio auth status
9
9
  corvio agents connect --provider codex --project . --json
10
+ corvio collaboration status --json --no-input
10
11
  corvio ask --prompt 'Summarize recurring launch risks' --json
11
12
  corvio files upload --file ./research.pdf --json
13
+ corvio files organize <asset_id> --instruction 'Create a reusable decision brief' --yes --json
14
+ corvio files operation <operation_id> --json
12
15
  corvio sync init --dir ./knowledge --root-node-id <node_id> --json
13
16
  corvio update check --json
14
17
  ```
15
18
 
16
- When no environment key is configured, run `corvio auth login`. Login creates one user-level `cvu_` control-plane credential; it does not freeze the browser's current Workspace. Run `corvio agents connect --project .` from the project you want to expose. The command verifies the selected Codex, Claude Code, or Copilot host before server mutation, then idempotently creates or restores the Agent for that provider/device/project binding, grants current/future Workspace coverage under live membership/role and ACL limits, saves the one-time `cvg_` in a `0600` multi-binding registry, and starts that project's listener. Another project gets another Agent. Ordinary data commands prefer the binding whose root contains the current directory. Use `corvio workspaces use`, `--workspace`, and `--agent` as explicit routing/reproducibility controls. Legacy Workspace-bound `cvk_`, `cvg_`, and version-1 credential files remain compatible and are never widened. Local sync stores only credential-free handles and revision/hash baselines in `.corvio/state.json`; Markdown lives below `corvio_docs/`, and conflicts require explicit resolution. Update checks compare npm's published `beta` tag with the API compatibility policy but never self-install. See [the Corvio CLI guide](https://corvio.ai/developers/cli) for the full command and receipt contract.
19
+ When no environment key is configured, run `corvio auth login`. Login creates one user-level `cvu_` control-plane credential; it does not freeze the browser's current Workspace. Run `corvio agents connect --project .` from the project you want to expose. The command verifies the selected Codex, Claude Code, or Copilot host before server mutation, then idempotently creates or restores the Agent for that provider/device/project binding, grants current/future Workspace coverage under live membership/role and ACL limits, saves the one-time `cvg_` in a `0600` multi-binding registry, and starts that project's listener. `corvio collaboration status` then checks a separate foreground requirement: the current host must also discover the official Skill at the live server contract version. A listener receives inbound `@Agent` work; it does not make an unrelated foreground coding session collaborate automatically. Another project gets another Agent. Ordinary data commands prefer the binding whose root contains the current directory. Use `corvio workspaces use`, `--workspace`, and `--agent` as explicit routing/reproducibility controls. Legacy Workspace-bound `cvk_`, `cvg_`, and version-1 credential files remain compatible and are never widened. Local sync stores only credential-free handles and revision/hash baselines in `.corvio/state.json`; Markdown lives below `corvio_docs/`, and conflicts require explicit resolution. Update checks compare npm's published `beta` tag with the API compatibility policy but never self-install. See [the Corvio CLI guide](https://corvio.ai/developers/cli) for the full command and receipt contract.
17
20
 
18
- 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 updates preflight the current revision when `--expected-revision` is omitted. When the executing principal is a connected Agent, `docs update` also requires `--change-summary`, creates a visible document-level comment after the guarded write, and returns its receipt; a comment failure is reported as a partial effect rather than silent success. 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.
21
+ 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 updates preflight the current revision when `--expected-revision` is omitted. When the executing principal is a connected Agent, `docs update` also requires `--change-summary`, creates a visible document-level comment after the guarded write, and returns 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.
22
+
23
+ File organization is asynchronous. A queued organization receipt is progress, not completion: continue from its stable operation ID with `corvio files operation <operation_id>` and only report completion after the terminal receipt exposes the resulting artifacts, Skill evaluation, and durable links.
19
24
 
20
25
  ## Project Agent collaboration
21
26
 
@@ -25,7 +30,7 @@ Connect the current project with the user credential. User and Agent credentials
25
30
  corvio agents connect --provider codex --project . --json
26
31
  ```
27
32
 
28
- Without `--display-name`, connect derives a provider-aware project name from the authenticated user, such as `Ada Codex · billing-service`; Settings remains the rename owner. The receipt distinguishes `Configured`, `CLI Ready`, `Listening`, and `Offline`, and includes the stable Agent, opaque project binding, Workspace scope, host permission/trigger policy, and Settings link. The default `workspace_write` profile uses the provider's native sandbox. `owner_only` is the safe trigger default. Allowing Workspace members to invoke the local project or granting unrestricted host access requires explicit `--yes`.
33
+ Without `--display-name`, connect derives a provider-aware project name from the authenticated user, such as `Ada Codex · billing-service`; Settings remains the rename owner. The receipt distinguishes `Configured`, `CLI Ready`, `Listening`, `Offline`, and `Foreground collaboration ready`, and includes the stable Agent, opaque project binding, Workspace scope, host permission/trigger policy, Skill status, and Settings link. The default `workspace_write` profile uses the provider's native sandbox. `owner_only` is the safe trigger default. Allowing Workspace members to invoke the local project or granting unrestricted host access requires explicit `--yes`.
29
34
 
30
35
  The listener first runs a read-only provider planning pass, then asks Corvio Query for exact origin-document and collaboration context. `local_project` work runs inside the bound project; online document writes remain Query/Writer-owned. The provider consumes the Query result before replying, and completion records exact event/lease/question/comment lineage plus provider, project fingerprint, claimed files/checks, and observed Git working-tree state. A custom handler remains available for advanced adapters:
31
36
 
package/dist/cli.js CHANGED
@@ -39,13 +39,14 @@ import {
39
39
  agentCredentialPath,
40
40
  } from "./core.js";
41
41
 
42
- const VERSION = "0.1.0-beta.14";
42
+ const VERSION = "0.1.0-beta.16";
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 CORVIO_SKILL_INSTALL_COMMAND = "npx skills add https://corvio.ai/developers/skills/corvio-operate-workspace/corvio-operate-workspace.zip";
49
50
  const GLOBAL_OPTIONS = Object.freeze([
50
51
  "api-base",
51
52
  "workspace",
@@ -67,7 +68,11 @@ const COMMAND_OPTIONS = Object.freeze({
67
68
  "workspaces:use": ["id"],
68
69
  "workspaces:create": ["name", "kind", "description", "idempotency-key"],
69
70
  "capabilities:": [],
70
- "ask:": ["prompt", "conversation-id", "sources", "allow-actions", "context-file", "idempotency-key"],
71
+ "collaboration:status": ["project", "provider"],
72
+ "ask:": [
73
+ "prompt", "conversation-id", "sources", "allow-actions", "context-file", "idempotency-key",
74
+ "processing-profile", "max-processing-profile", "scan-mode", "skills-extraction-mode", "caller-model",
75
+ ],
71
76
  "agents:list": ["include-revoked"],
72
77
  "agents:connect": [
73
78
  "handle", "display-name", "provider", "project", "permission-profile", "trigger-policy",
@@ -85,7 +90,11 @@ const COMMAND_OPTIONS = Object.freeze({
85
90
  "agent:comment": ["document-id", "body", "selection-file", "start-new-chain", "idempotency-key"],
86
91
  "agent:reply": ["document-id", "id", "body", "source-event-id", "source-event-lease-token", "start-new-chain", "idempotency-key"],
87
92
  "agent:status": ["document-id", "id", "status"],
88
- "agent:run": ["once", "handler", "provider", "worker-id", "lease-seconds", "wait-seconds", "sources", "project"],
93
+ "agent:closeout": ["document-id", "thread-id", "body", "status", "idempotency-key"],
94
+ "agent:run": [
95
+ "once", "handler", "provider", "worker-id", "lease-seconds", "wait-seconds", "sources", "project",
96
+ "processing-profile", "max-processing-profile", "scan-mode", "skills-extraction-mode", "caller-model",
97
+ ],
89
98
  "questions:list": ["limit", "cursor", "conversation-id"],
90
99
  "questions:get": ["id"],
91
100
  "conversations:list": ["limit", "cursor"],
@@ -105,8 +114,15 @@ const COMMAND_OPTIONS = Object.freeze({
105
114
  "files:list": ["limit"],
106
115
  "files:get": ["id"],
107
116
  "files:download": ["id", "output"],
108
- "files:upload": ["file", "mime-type"],
109
- "files:organize": ["id", "instruction", "target-root-node-id"],
117
+ "files:upload": [
118
+ "file", "mime-type", "organize", "instruction", "target-root-node-id", "additional-asset-ids",
119
+ "processing-profile", "max-processing-profile", "scan-mode", "skills-extraction-mode", "caller-model",
120
+ ],
121
+ "files:organize": [
122
+ "id", "additional-asset-ids", "instruction", "target-root-node-id",
123
+ "processing-profile", "max-processing-profile", "scan-mode", "skills-extraction-mode", "caller-model",
124
+ ],
125
+ "files:operation": ["id"],
110
126
  "files:open": ["id"],
111
127
  "sync:init": ["root-node-id", "dir", "name", "force"],
112
128
  "sync:status": ["dir"],
@@ -123,15 +139,16 @@ Usage:
123
139
  corvio auth login|status|logout
124
140
  corvio workspaces list|use|current|create [id]
125
141
  corvio capabilities
126
- corvio ask --prompt <text> [--conversation-id <id>] [--allow-actions]
142
+ corvio collaboration status [--project <path>] [--provider codex|claude_code|copilot]
143
+ corvio ask --prompt <text> [--conversation-id <id>] [--allow-actions] [--processing-profile auto|economy|standard|deep]
127
144
  corvio agents list|connect|create|update|rotate [id]
128
- corvio agent claim|renew|complete|fail|comments|comment|reply|status|run [id]
145
+ corvio agent claim|renew|complete|fail|comments|comment|reply|status|closeout|run [id]
129
146
  corvio questions list|get [id]
130
147
  corvio conversations list|get [id]
131
148
  corvio search <query>
132
149
  corvio docs list|get|create|update|move|archive|restore|share|open [id]
133
150
  corvio projects list|create
134
- corvio files list|get|download|upload|organize|open [id]
151
+ corvio files list|get|download|upload|organize|operation|open [id]
135
152
  corvio sync init|status|plan|pull|push|resolve
136
153
  corvio update check
137
154
 
@@ -151,15 +168,16 @@ const COMMAND_HELP = Object.freeze({
151
168
  auth: `Usage: corvio auth login|status|logout\n\nAuthenticate with a user-level device link, inspect the current principal, or revoke the stored credential.`,
152
169
  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.`,
153
170
  capabilities: `Usage: corvio capabilities [--json]\n\nShow live API resources, principal scopes, selected Workspace status, and CLI compatibility.`,
154
- ask: `Usage: corvio ask --prompt <text> [--conversation-id <id>] [--sources workspace,web,memory] [--allow-actions] [--context-file <json>] [--idempotency-key <key>]`,
155
- 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. 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.`,
156
- 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 run [--provider codex|claude_code|copilot|custom] [--handler <executable>] [--agent <id>] [--once]\n\nUse mentions before commenting when the target is uncertain; aliases resolve to stable user or Agent IDs and ambiguity fails closed. 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.`,
171
+ collaboration: `Usage:\n corvio collaboration status [--project <path>] [--provider codex|claude_code|copilot]\n\nVerify that the current project has a bound Corvio Agent, the official collaboration Skill is discoverable at the live contract version, and the inbound listener state is reported separately. This is the foreground work-loop readiness check; a listening process alone is not enough.`,
172
+ ask: `Usage: corvio ask --prompt <text> [--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\nSkill extraction mode always requires --allow-actions; answer-only questions may use auto or off.`,
173
+ 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 foreground Skill readiness separately; run corvio collaboration status after installing or refreshing the Skill. 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.`,
174
+ 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.`,
157
175
  questions: `Usage:\n corvio questions list [--limit <n>] [--cursor <cursor>] [--conversation-id <id>]\n corvio questions get <question-id>`,
158
176
  conversations: `Usage:\n corvio conversations list [--limit <n>] [--cursor <cursor>]\n corvio conversations get <conversation-id>`,
159
177
  search: `Usage: corvio search <query> [--limit <n>] [--page-id <id>] [--node-id <id>]`,
160
- docs: `Usage:\n corvio docs list [--limit <n>] [--cursor <cursor>] [--lifecycle active|archived]\n corvio docs get <id> [--output <path>]\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\nDirect document commands operate on canonical Page/Markdown. Use \`corvio ask --allow-actions\` for Spreadsheet, Presentation, Code, HTML Artifact, or semantic organization. Agent-authenticated document updates 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 comments|reply|status\` to return verified work to that same collaboration surface.`,
178
+ docs: `Usage:\n corvio docs list [--limit <n>] [--cursor <cursor>] [--lifecycle active|archived]\n corvio docs get <id> [--output <path>]\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\nDirect document commands operate on canonical Page/Markdown. Use \`corvio ask --allow-actions\` for Spreadsheet, Presentation, Code, HTML Artifact, or semantic organization. Agent-authenticated document updates 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.`,
161
179
  projects: `Usage:\n corvio projects list [--limit <n>]\n corvio projects create --title <title> [--idempotency-key <key>]\n\nProjects are durable grouping owners in the Corvio Docs tree. Reuse a matching Project instead of creating one ceremonial Project per file. 'corvio folders' is an alias.`,
162
- files: `Usage:\n corvio files list [--limit <n>]\n corvio files get <id>\n corvio files download <id> [--output <path>]\n corvio files upload --file <path> [--mime-type <type>]\n corvio files organize <id> --instruction <text> --yes [--target-root-node-id <id>]\n corvio files open <id>`,
180
+ files: `Usage:\n corvio files list [--limit <n>]\n corvio files get <id>\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>] [--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 open <id>\n\nUpload alone preserves the original file. Organization is asynchronous: use 'files operation' to read its terminal result, derived artifacts, Skill evaluation, and links before claiming completion.`,
163
181
  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>]`,
164
182
  update: `Usage: corvio update check [--json]\n\nCompare this executable with the reviewed API policy and the npm beta dist-tag.`,
165
183
  });
@@ -185,6 +203,7 @@ function normalizeInvocation(command, action) {
185
203
  projects: "list",
186
204
  files: "list",
187
205
  agents: "list",
206
+ collaboration: "status",
188
207
  agent: "claim",
189
208
  sync: "status",
190
209
  update: "check",
@@ -219,6 +238,7 @@ function validateInvocation(options, positionals) {
219
238
  "files:get",
220
239
  "files:download",
221
240
  "files:organize",
241
+ "files:operation",
222
242
  "files:open",
223
243
  "agents:update",
224
244
  "agents:rotate",
@@ -650,6 +670,141 @@ async function capabilities(options) {
650
670
  }, { json: options.json });
651
671
  }
652
672
 
673
+ async function codexPluginGuidanceCandidates(codexHome) {
674
+ const cacheRoot = join(codexHome, "plugins", "cache");
675
+ const listDirectories = async (directory) => {
676
+ try {
677
+ return (await readdir(directory, { withFileTypes: true }))
678
+ .filter((entry) => entry.isDirectory())
679
+ .slice(0, 256)
680
+ .map((entry) => entry.name);
681
+ } catch (error) {
682
+ if (["ENOENT", "ENOTDIR", "EACCES"].includes(error?.code)) return [];
683
+ throw error;
684
+ }
685
+ };
686
+ const candidates = [];
687
+ for (const marketplace of await listDirectories(cacheRoot)) {
688
+ const marketplaceRoot = join(cacheRoot, marketplace);
689
+ for (const plugin of await listDirectories(marketplaceRoot)) {
690
+ const pluginRoot = join(marketplaceRoot, plugin);
691
+ for (const version of await listDirectories(pluginRoot)) {
692
+ candidates.push({
693
+ scope: "codex_plugin_cache",
694
+ path: join(pluginRoot, version, "skills", "corvio-operate-workspace", "SKILL.md"),
695
+ });
696
+ }
697
+ }
698
+ }
699
+ return candidates;
700
+ }
701
+
702
+ async function inspectCollaborationGuidance(provider, projectRoot, expectedContractVersion) {
703
+ const candidates = [
704
+ { scope: "project_agents", path: join(projectRoot, ".agents", "skills", "corvio-operate-workspace", "SKILL.md") },
705
+ { scope: "user_agents", path: join(homedir(), ".agents", "skills", "corvio-operate-workspace", "SKILL.md") },
706
+ ];
707
+ if (provider === "codex") {
708
+ const codexHome = String(process.env.CODEX_HOME || join(homedir(), ".codex"));
709
+ candidates.push({
710
+ scope: "user_codex",
711
+ path: join(codexHome, "skills", "corvio-operate-workspace", "SKILL.md"),
712
+ });
713
+ candidates.push(...await codexPluginGuidanceCandidates(codexHome));
714
+ }
715
+ if (provider === "claude_code") {
716
+ candidates.push(
717
+ { scope: "project_claude", path: join(projectRoot, ".claude", "skills", "corvio-operate-workspace", "SKILL.md") },
718
+ {
719
+ scope: "user_claude",
720
+ path: join(String(process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude")), "skills", "corvio-operate-workspace", "SKILL.md"),
721
+ },
722
+ );
723
+ }
724
+ const found = [];
725
+ for (const candidate of candidates) {
726
+ try {
727
+ const source = await readFile(candidate.path, "utf8");
728
+ if (!/^name:\s*corvio-operate-workspace\s*$/m.test(source)) continue;
729
+ found.push({
730
+ scope: candidate.scope,
731
+ current: Boolean(expectedContractVersion)
732
+ && source.includes(`contract version \`${expectedContractVersion}\``),
733
+ });
734
+ } catch (error) {
735
+ if (error?.code !== "ENOENT") throw error;
736
+ }
737
+ }
738
+ const current = found.filter((item) => item.current);
739
+ const status = !expectedContractVersion
740
+ ? "contract_unavailable"
741
+ : current.length
742
+ ? "ready"
743
+ : found.length
744
+ ? "stale"
745
+ : "missing";
746
+ return {
747
+ status,
748
+ expected_contract_version: expectedContractVersion || null,
749
+ discovered_scopes: found.map((item) => item.scope),
750
+ current_scopes: current.map((item) => item.scope),
751
+ install_command: status === "missing" || status === "stale" ? CORVIO_SKILL_INSTALL_COMMAND : null,
752
+ };
753
+ }
754
+
755
+ async function collaborationStatus(options) {
756
+ const projectRoot = await findProjectRoot(options.project);
757
+ const credential = await readAgentCredential(process.env, {
758
+ required: false,
759
+ agentId: String(options.agent || "").trim() || null,
760
+ projectRoot,
761
+ fallbackToActive: false,
762
+ });
763
+ const provider = String(options.provider || credential?.metadata?.provider || "codex");
764
+ const api = new ApiClient({ baseUrl: baseUrl(options), userAgent: USER_AGENT });
765
+ const index = await api.request("", { auth: false });
766
+ const liveContract = dictLike(index?.coding_agent_collaboration);
767
+ const serverVersion = String(liveContract.version || "").trim() || null;
768
+ const guidance = await inspectCollaborationGuidance(provider, projectRoot, serverVersion);
769
+ const bindingId = String(credential?.metadata?.binding_id || "").trim();
770
+ const runnerState = await readAgentRunnerState();
771
+ const listener = bindingId ? dictLike(runnerState?.listeners?.[bindingId]) : {};
772
+ const listenerStatus = processIsRunning(Number(listener.pid)) ? "listening" : "offline";
773
+ let status = "ready";
774
+ if (!credential) status = "needs_connection";
775
+ else if (!serverVersion) status = "contract_unavailable";
776
+ else if (guidance.status === "missing") status = "needs_skill";
777
+ else if (guidance.status === "stale") status = "stale_skill";
778
+ const nextActions = [];
779
+ if (!credential) nextActions.push(`corvio agents connect --provider ${provider} --project .`);
780
+ if (guidance.install_command) nextActions.push(guidance.install_command);
781
+ if (!serverVersion) nextActions.push("corvio update check --json");
782
+ writeResult({
783
+ status,
784
+ foreground_collaboration_ready: status === "ready",
785
+ contract_version: serverVersion,
786
+ server_contract_version: serverVersion,
787
+ project_agent: credential
788
+ ? {
789
+ connected: true,
790
+ id: credential.metadata?.agent_id || null,
791
+ provider,
792
+ binding_id: bindingId || null,
793
+ }
794
+ : { connected: false, id: null, provider, binding_id: null },
795
+ host_guidance: guidance,
796
+ listener_status: listenerStatus,
797
+ listener_note: "The listener receives inbound @Agent work; it does not activate foreground Coding-Agent collaboration.",
798
+ synchronization: {
799
+ mode: "host_guided_foreground",
800
+ automatic_transcript_capture: false,
801
+ reason: "The host selects decision-relevant context and valuable results; Corvio never records arbitrary chats or local files in the background.",
802
+ work_loop: liveContract.work_loop || ["ground", "work", "checkpoint", "materialize", "verify", "return"],
803
+ },
804
+ next_actions: nextActions,
805
+ }, { json: options.json });
806
+ }
807
+
653
808
  async function workspaces(options, action, id) {
654
809
  const credential = await readWorkspaceCredential();
655
810
  if (!credential) {
@@ -742,14 +897,30 @@ async function ask(options, positionals) {
742
897
  const context = options["context-file"]
743
898
  ? await readJsonObject(options["context-file"], "--context-file")
744
899
  : null;
900
+ const processingProfile = enumOption(options, "processing-profile", ["auto", "economy", "standard", "deep"], "auto");
901
+ const maxProcessingProfile = optionalEnumOption(options, "max-processing-profile", ["economy", "standard", "deep"]);
902
+ const scanMode = enumOption(options, "scan-mode", ["auto", "always", "off"], "auto");
903
+ const skillsExtractionMode = enumOption(options, "skills-extraction-mode", ["auto", "always", "off"], "auto");
904
+ if (skillsExtractionMode === "always" && !options["allow-actions"]) {
905
+ throw new CliError("--skills-extraction-mode always requires --allow-actions.", {
906
+ code: "skills_extraction_requires_actions",
907
+ });
908
+ }
745
909
  const result = await api.request("/questions", {
746
910
  method: "POST",
747
- headers: { "idempotency-key": idempotencyKey(options) },
911
+ headers: {
912
+ "idempotency-key": idempotencyKey(options),
913
+ ...(options["caller-model"] ? { "x-corvio-caller-model": String(options["caller-model"]) } : {}),
914
+ },
748
915
  body: {
749
916
  prompt,
750
917
  conversation_id: options["conversation-id"] || null,
751
918
  mode: options["allow-actions"] ? "allow_actions" : "answer_only",
752
919
  sources: listOption(options, "sources", ["workspace", "web", "memory"]),
920
+ processing_profile: processingProfile,
921
+ max_processing_profile: maxProcessingProfile,
922
+ scan_mode: scanMode,
923
+ skills_extraction_mode: skillsExtractionMode,
753
924
  context,
754
925
  },
755
926
  timeoutMs: 330_000,
@@ -766,6 +937,21 @@ async function ask(options, positionals) {
766
937
  writeResult({ ...result, routing: execution.routing }, { json: options.json });
767
938
  }
768
939
 
940
+ function enumOption(options, name, allowed, fallback) {
941
+ const value = String(options[name] ?? fallback).trim().toLowerCase();
942
+ if (!allowed.includes(value)) {
943
+ throw new CliError(`--${name} must be one of: ${allowed.join(", ")}.`, {
944
+ code: `${name.replaceAll("-", "_")}_invalid`,
945
+ });
946
+ }
947
+ return value;
948
+ }
949
+
950
+ function optionalEnumOption(options, name, allowed) {
951
+ if (options[name] === undefined) return null;
952
+ return enumOption(options, name, allowed, null);
953
+ }
954
+
769
955
  function providerChildEnvironment(extra = {}) {
770
956
  const allowedNames = new Set([
771
957
  "PATH", "HOME", "USER", "LOGNAME", "SHELL", "TERM", "TMPDIR",
@@ -1224,6 +1410,22 @@ async function agents(options, action, id) {
1224
1410
  const listener = options["no-listen"]
1225
1411
  ? { status: "offline", pid: null, reused: false, log: null }
1226
1412
  : await ensureAgentListener({ options, provider, agentId: agent.id, binding });
1413
+ let collaborationVersion = null;
1414
+ try {
1415
+ const collaborationIndex = await api.request("", { auth: false });
1416
+ collaborationVersion = String(
1417
+ dictLike(collaborationIndex?.coding_agent_collaboration).version || "",
1418
+ ).trim() || null;
1419
+ } catch {
1420
+ // The Agent binding and provider readiness are already durable. Keep this
1421
+ // later collaboration check explicit instead of turning a settled connect
1422
+ // into an ambiguous failure.
1423
+ }
1424
+ const hostGuidance = await inspectCollaborationGuidance(
1425
+ provider,
1426
+ binding.project_root,
1427
+ collaborationVersion,
1428
+ );
1227
1429
  result = {
1228
1430
  connected: true,
1229
1431
  idempotent: Boolean(result.idempotent),
@@ -1258,8 +1460,20 @@ async function agents(options, action, id) {
1258
1460
  reused: listener.reused,
1259
1461
  log: listener.log,
1260
1462
  },
1463
+ foreground_collaboration: {
1464
+ status: hostGuidance.status === "ready"
1465
+ ? "ready"
1466
+ : hostGuidance.status === "contract_unavailable"
1467
+ ? "contract_unavailable"
1468
+ : "needs_skill",
1469
+ ready: hostGuidance.status === "ready",
1470
+ host_guidance: hostGuidance,
1471
+ note: "The inbound listener and foreground collaboration are separate readiness facts.",
1472
+ },
1261
1473
  settings_url: result.settings_url,
1262
- next_command: `corvio agent run --provider ${provider} --agent ${agent.id}`,
1474
+ next_command: hostGuidance.status === "ready"
1475
+ ? "corvio collaboration status --json --no-input"
1476
+ : hostGuidance.install_command || "corvio collaboration status --json --no-input",
1263
1477
  };
1264
1478
  } else if (action === "create") {
1265
1479
  result = await api.request("/agents", {
@@ -1646,6 +1860,11 @@ async function requestAgentCorvioQuestion(workspaceApi, event, leaseToken, {
1646
1860
  round,
1647
1861
  conversationId = null,
1648
1862
  priorTurn = null,
1863
+ processingProfile = "auto",
1864
+ maxProcessingProfile = null,
1865
+ scanMode = "auto",
1866
+ skillsExtractionMode = "auto",
1867
+ callerModel = null,
1649
1868
  }) {
1650
1869
  const question = await workspaceApi.request("/questions", {
1651
1870
  method: "POST",
@@ -1655,12 +1874,17 @@ async function requestAgentCorvioQuestion(workspaceApi, event, leaseToken, {
1655
1874
  : `agent-event:${event.id}:query:${round}`,
1656
1875
  "x-corvio-agent-event-id": event.id,
1657
1876
  "x-corvio-agent-event-lease-token": leaseToken,
1877
+ ...(callerModel ? { "x-corvio-caller-model": callerModel } : {}),
1658
1878
  },
1659
1879
  body: {
1660
1880
  prompt: instruction,
1661
1881
  ...(conversationId ? { conversation_id: conversationId } : {}),
1662
1882
  mode,
1663
1883
  sources: [],
1884
+ processing_profile: processingProfile,
1885
+ max_processing_profile: maxProcessingProfile,
1886
+ scan_mode: scanMode,
1887
+ skills_extraction_mode: skillsExtractionMode,
1664
1888
  context: {
1665
1889
  agent_event: agentEventContext(event),
1666
1890
  collaboration: event.context,
@@ -1792,11 +2016,24 @@ async function processClaimedAgentEvent(api, event, options) {
1792
2016
  const decision = options.handler
1793
2017
  ? { execution_target: "corvio_document", ...(await runAgentHandler(options.handler, event)) }
1794
2018
  : await runProjectProviderPlan(event, provider, binding);
2019
+ const questionExecution = {
2020
+ processingProfile: enumOption(options, "processing-profile", ["auto", "economy", "standard", "deep"], "auto"),
2021
+ maxProcessingProfile: optionalEnumOption(options, "max-processing-profile", ["economy", "standard", "deep"]),
2022
+ scanMode: enumOption(options, "scan-mode", ["auto", "always", "off"], "auto"),
2023
+ skillsExtractionMode: enumOption(options, "skills-extraction-mode", ["auto", "always", "off"], "auto"),
2024
+ callerModel: String(options["caller-model"] || "").trim() || null,
2025
+ };
2026
+ const initialQuestionMode =
2027
+ decision.execution_target === "corvio_document" ||
2028
+ questionExecution.skillsExtractionMode === "always"
2029
+ ? "allow_actions"
2030
+ : "answer_only";
1795
2031
  const workspaceApi = await agentClient(options, String(event.workspace_id || ""));
1796
2032
  let question = await requestAgentCorvioQuestion(workspaceApi, event, leaseToken, {
1797
2033
  instruction: decision.instruction,
1798
- mode: decision.execution_target === "corvio_document" ? "allow_actions" : "answer_only",
2034
+ mode: initialQuestionMode,
1799
2035
  round: 1,
2036
+ ...questionExecution,
1800
2037
  });
1801
2038
  const questions = [question];
1802
2039
  const instructions = new Set([decision.instruction.trim().toLowerCase()]);
@@ -1821,7 +2058,7 @@ async function processClaimedAgentEvent(api, event, options) {
1821
2058
  round: 1,
1822
2059
  question_id: question.id || null,
1823
2060
  conversation_id: question.conversation_id || null,
1824
- requested_mode: decision.execution_target === "corvio_document" ? "allow_actions" : "answer_only",
2061
+ requested_mode: initialQuestionMode,
1825
2062
  provider_outcome: providerExecution.outcome,
1826
2063
  next_action: providerExecution.next_action,
1827
2064
  }];
@@ -1857,13 +2094,17 @@ async function processClaimedAgentEvent(api, event, options) {
1857
2094
  break;
1858
2095
  }
1859
2096
  instructions.add(normalizedInstruction);
1860
- const requestedMode = providerExecution.corvio_mode;
2097
+ const requestedMode =
2098
+ questionExecution.skillsExtractionMode === "always"
2099
+ ? "allow_actions"
2100
+ : providerExecution.corvio_mode;
1861
2101
  question = await requestAgentCorvioQuestion(workspaceApi, event, leaseToken, {
1862
2102
  instruction: nextInstruction,
1863
2103
  mode: requestedMode,
1864
2104
  round: questions.length + 1,
1865
2105
  conversationId: question.conversation_id || null,
1866
2106
  priorTurn: providerExecution,
2107
+ ...questionExecution,
1867
2108
  });
1868
2109
  questions.push(question);
1869
2110
  providerExecution = await runProjectProviderExecution(
@@ -2196,6 +2437,97 @@ async function agentCommand(options, action, id) {
2196
2437
  method: "POST",
2197
2438
  body: { status: option(options, "status", { required: true }), entry_point: "external_agent_cli_status" },
2198
2439
  });
2440
+ } else if (action === "closeout") {
2441
+ const documentId = option(options, "document-id", { required: true });
2442
+ const threadId = String(options["thread-id"] || "").trim() || null;
2443
+ const bodyText = option(options, "body", { required: true });
2444
+ const status = String(options.status || "open").trim().toLowerCase();
2445
+ if (!["open", "resolved"].includes(status)) {
2446
+ throw new CliError("--status must be open or resolved.", { code: "comment_status_invalid" });
2447
+ }
2448
+ const before = await api.request(`/documents/${resourcePath(documentId)}/comments`);
2449
+ if (threadId && !Array.isArray(before?.threads)) {
2450
+ throw new CliError("The current comment readback is invalid.", { code: "comment_readback_invalid" });
2451
+ }
2452
+ if (threadId && !before.threads.some((thread) => String(thread.id) === threadId)) {
2453
+ throw new CliError("The originating comment thread was not found on this document.", {
2454
+ code: "comment_thread_not_found",
2455
+ });
2456
+ }
2457
+ const stableKey = idempotencyKey(options);
2458
+ const mentionTargets = await resolveAgentMentionTargets(api, documentId, bodyText);
2459
+ const comment = threadId
2460
+ ? await api.request(`/documents/${resourcePath(documentId)}/comments/${resourcePath(threadId)}/replies`, {
2461
+ method: "POST",
2462
+ headers: { "idempotency-key": stableKey },
2463
+ body: {
2464
+ body: bodyText,
2465
+ mention_targets: mentionTargets,
2466
+ entry_point: "external_agent_cli_closeout",
2467
+ start_new_chain: false,
2468
+ },
2469
+ })
2470
+ : await api.request(`/documents/${resourcePath(documentId)}/comments`, {
2471
+ method: "POST",
2472
+ headers: { "idempotency-key": stableKey },
2473
+ body: {
2474
+ body: bodyText,
2475
+ mention_targets: mentionTargets,
2476
+ selection: { target_scope: "document" },
2477
+ entry_point: "external_agent_cli_closeout",
2478
+ start_new_chain: false,
2479
+ },
2480
+ });
2481
+ const closeoutThreadId = String(comment?.id || threadId || "");
2482
+ try {
2483
+ if (status === "resolved" && String(comment?.status || "") !== "resolved") {
2484
+ await api.request(`/documents/${resourcePath(documentId)}/comments/${resourcePath(closeoutThreadId)}/status`, {
2485
+ method: "POST",
2486
+ body: { status: "resolved", entry_point: "external_agent_cli_closeout" },
2487
+ });
2488
+ }
2489
+ } catch {
2490
+ throw new CliError(
2491
+ "The result comment was committed, but the requested closeout status failed. Keep the thread open and retry the status update after readback.",
2492
+ {
2493
+ code: "comment_closeout_partial",
2494
+ exitCode: EXIT.REMOTE,
2495
+ details: { document_id: documentId, thread_id: closeoutThreadId, comment },
2496
+ },
2497
+ );
2498
+ }
2499
+ const [document, comments] = await Promise.all([
2500
+ api.request(`/documents/${resourcePath(documentId)}`),
2501
+ api.request(`/documents/${resourcePath(documentId)}/comments`),
2502
+ ]);
2503
+ const thread = Array.isArray(comments?.threads)
2504
+ ? comments.threads.find((item) => String(item.id) === closeoutThreadId) || null
2505
+ : null;
2506
+ if (!thread) {
2507
+ throw new CliError(
2508
+ "The result comment was committed, but its current thread could not be read back.",
2509
+ {
2510
+ code: "comment_closeout_readback_failed",
2511
+ exitCode: EXIT.REMOTE,
2512
+ details: { document_id: documentId, thread_id: closeoutThreadId, comment },
2513
+ },
2514
+ );
2515
+ }
2516
+ result = {
2517
+ document: {
2518
+ id: document?.id || documentId,
2519
+ node_id: document?.node_id || null,
2520
+ title: document?.title || null,
2521
+ doc_type: document?.doc_type || null,
2522
+ content_revision: document?.content_revision ?? null,
2523
+ url: document?.url || null,
2524
+ },
2525
+ thread,
2526
+ completed: status === "resolved" && thread?.status === "resolved",
2527
+ remaining_boundary: status === "resolved" && thread?.status === "resolved"
2528
+ ? null
2529
+ : "The originating thread remains open for incomplete or unverified work.",
2530
+ };
2199
2531
  } else {
2200
2532
  throw new CliError("Unknown agent runtime action.");
2201
2533
  }
@@ -2570,25 +2902,46 @@ async function files(options, action, id) {
2570
2902
  exitCode: EXIT.REMOTE,
2571
2903
  });
2572
2904
  }
2905
+ if (options.organize) {
2906
+ requireConfirmation(options, "Starting semantic organization for the uploaded file");
2907
+ const organization = await api.request(`/files/${resourcePath(result.id)}:organize`, {
2908
+ method: "POST",
2909
+ headers: options["caller-model"] ? { "x-corvio-caller-model": String(options["caller-model"]) } : {},
2910
+ body: fileOrganizationBody(options),
2911
+ });
2912
+ result = { file: result, organization };
2913
+ }
2573
2914
  } else if (action === "organize") {
2574
2915
  requireConfirmation(options, "Starting a file organization run");
2575
2916
  result = await api.request(`/files/${resourcePath(assetId || option(options, "id", { required: true }))}:organize`, {
2576
2917
  method: "POST",
2577
- body: {
2578
- instruction: option(options, "instruction", { required: true }),
2579
- target_root_node_id: options["target-root-node-id"] || null,
2580
- },
2918
+ headers: options["caller-model"] ? { "x-corvio-caller-model": String(options["caller-model"]) } : {},
2919
+ body: fileOrganizationBody(options),
2581
2920
  });
2921
+ } else if (action === "operation") {
2922
+ result = await api.request(`/file-operations/${resourcePath(assetId || option(options, "id", { required: true }))}`);
2582
2923
  } else if (action === "open") {
2583
2924
  const targetId = assetId || option(options, "id", { required: true });
2584
2925
  const target = `${String(process.env.CORVIO_APP_BASE || DEFAULT_APP_BASE).replace(/\/+$/, "")}/app/a/${resourcePath(targetId)}`;
2585
2926
  result = { file_id: targetId, url: target, opened: options["no-input"] ? false : await openBrowser(target) };
2586
2927
  } else {
2587
- throw new CliError("files action must be list, get, download, upload, organize, or open.");
2928
+ throw new CliError("files action must be list, get, download, upload, organize, operation, or open.");
2588
2929
  }
2589
2930
  writeResult(result, { json: options.json });
2590
2931
  }
2591
2932
 
2933
+ function fileOrganizationBody(options) {
2934
+ return {
2935
+ instruction: option(options, "instruction", { required: true }),
2936
+ target_root_node_id: options["target-root-node-id"] || null,
2937
+ additional_asset_ids: listOption(options, "additional-asset-ids", []),
2938
+ processing_profile: enumOption(options, "processing-profile", ["auto", "economy", "standard", "deep"], "auto"),
2939
+ max_processing_profile: optionalEnumOption(options, "max-processing-profile", ["economy", "standard", "deep"]),
2940
+ scan_mode: enumOption(options, "scan-mode", ["auto", "always", "off"], "auto"),
2941
+ skills_extraction_mode: enumOption(options, "skills-extraction-mode", ["auto", "always", "off"], "auto"),
2942
+ };
2943
+ }
2944
+
2592
2945
  function sha256(content) {
2593
2946
  return createHash("sha256").update(content).digest("hex");
2594
2947
  }
@@ -3037,6 +3390,7 @@ async function main({ options, positionals }) {
3037
3390
  return workspaces(options, action || "list", id);
3038
3391
  }
3039
3392
  if (command === "capabilities") return capabilities(options);
3393
+ if (command === "collaboration") return collaborationStatus(options);
3040
3394
  if (command === "ask") return ask(options, [action, id, ...rest].filter(Boolean));
3041
3395
  if (command === "agents") return agents(options, action || "list", id);
3042
3396
  if (command === "agent") return agentCommand(options, action || "claim", id);
package/dist/core.js CHANGED
@@ -93,6 +93,7 @@ export function parseArgv(argv) {
93
93
  "no-input",
94
94
  "yes",
95
95
  "allow-actions",
96
+ "organize",
96
97
  "private-link",
97
98
  "force",
98
99
  "once",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@corvio/cli",
3
- "version": "0.1.0-beta.14",
3
+ "version": "0.1.0-beta.16",
4
4
  "description": "Official Corvio Workspace CLI for questions, knowledge search, documents, and Agent workflows.",
5
5
  "type": "module",
6
6
  "bin": {