Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions contrib/skills/shared/oo/references/connector-execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,8 @@ the user said:
- If the user does not mention any team, add nothing extra. The run then uses
the team selected by the `OO_TEAM_ID` / `OO_TEAM_NAME` environment variables
when set, then the default team saved on their account when one is set, and
their personal identity otherwise — omitting the flags does not force a
personal run. Check `oo team current` when you need to know which one applies.
the server-side default team otherwise. Check `oo team current` when you need
to know which one applies.
- If the user asks to run as a specific team (for example "run this as Acme" or
"use my Acme team"), add `--team "<name>"`, using the team name the user gave:

Expand All @@ -194,13 +194,15 @@ oo connector proxy "<serviceName>" \
--json
```

- If the user has a default team (from the env variables or the config default)
but explicitly asks for this one run to be personal, add `--personal`. This is
the only way to force a personal run when a default team is in effect.
- There is no personal run against the OOMOL connector: every run is
attributed to a team. With no flag, the team comes from `OO_TEAM_ID` /
`OO_TEAM_NAME`, then the saved account default, and otherwise the server
applies the account's default team. `--team` overrides all of those for one
run. A self-hosted connector has no team concept: it rejects `--team` and
ignores the env variables and the saved default.

Facts:

- `--team` and `--personal` cannot be combined.
- Do not guess a team name. If the user is vague about which team to use, ask
before running.
- The action schema itself is identity-independent, so `oo connector schema`
Expand Down
180 changes: 68 additions & 112 deletions docs/commands.md

Large diffs are not rendered by default.

125 changes: 47 additions & 78 deletions docs/commands.zh-CN.md

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions docs/self-hosted-connector.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,10 @@ A self-hosted runtime exposes a smaller surface than the OOMOL service. The CLI
adapts as follows:

- **Team identity is not supported.** `--team` is rejected with exit code `2` on
`oo connector run`, `oo connector proxy`, and `oo connector apps`, and any
`oo connector run`, `oo connector proxy`, `oo connector apps`, and
`oo connector search` / `oo search`, and any
saved account default team and the `OO_TEAM_ID` / `OO_TEAM_NAME`
environment variables are ignored. `--personal` is accepted (it is already
the effective behavior).
environment variables are ignored.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- **Async lifecycle waiting is unavailable.** `--wait` and `--wait-result` fail
with the existing "unsupported" errors because the self-hosted runtime does
not expose the async result-lifecycle contract.
Expand Down Expand Up @@ -364,5 +364,5 @@ and manage runtime tokens. The CLI references it in login output and in the
| `The connector server rejected the request (HTTP 401).` (exit 1) | The server requires a token. Create one at `<url>/access` and pass it with `--token`. |
| `The server at ... did not return a connector health response.` (exit 1) | The URL points at something that is not a connector server, or `/v1/health` does not return the expected envelope. |
| `Could not reach the self-hosted connector at ...` (during a command) | The saved server is down. Start it, or run `oo connector login` to reconfigure. This is not a sandbox or permission problem — do not retry with elevated permissions. |
| `The --team option is not supported by a self-hosted connector.` (exit 2) | Self-hosted runtimes have no team identity. Drop `--team`, or use `--personal`. |
| `The --team option is not supported by a self-hosted connector.` (exit 2) | Self-hosted runtimes have no team identity. Drop `--team`. |
| A command needs an OOMOL account | Non-connector features (file upload, LLM, skills) still require `oo auth login`. |
10 changes: 5 additions & 5 deletions docs/self-hosted-connector.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,10 @@ oo connector run gmail --action send_email --data '@payload.json'

自部署运行时暴露的能力比 OOMOL 服务更小,CLI 会做如下适配:

- **不支持团队身份。** `oo connector run`、`oo connector proxy``oo connector
apps` 上的 `--team` 会以退出码 `2` 被拒绝,账号保存的默认团队
和 `OO_TEAM_ID` / `OO_TEAM_NAME` 环境变量都会被忽略;`--personal` 可以使用
(它本就是实际行为)
- **不支持团队身份。** `oo connector run`、`oo connector proxy``oo connector
apps` 与 `oo connector search` / `oo search` 上的 `--team` 会以退出码 `2`
被拒绝,账号保存的默认团队
和 `OO_TEAM_ID` / `OO_TEAM_NAME` 环境变量都会被忽略
- **无法等待异步生命周期。** `--wait` 与 `--wait-result` 会以既有的「不支持」
错误失败,因为自部署运行时未暴露异步结果生命周期契约。
- **Proxy 取决于服务支持。** 只有当你的服务实现了 proxy 端点时,
Expand Down Expand Up @@ -327,5 +327,5 @@ CLI 会发送 `Content-Type: application/json`,请求体形如:
| `The connector server rejected the request (HTTP 401).`(退出码 1) | 服务需要令牌。请在 `<url>/access` 创建令牌,并通过 `--token` 传入。 |
| `The server at ... did not return a connector health response.`(退出码 1) | URL 指向的并非 connector 服务,或 `/v1/health` 未返回预期信封。 |
| `Could not reach the self-hosted connector at ...`(执行命令时) | 已保存的服务已宕机。请启动它,或运行 `oo connector login` 重新配置。这不是沙箱或权限问题,请勿以提升权限重试。 |
| `The --team option is not supported by a self-hosted connector.`(退出码 2) | 自部署运行时没有团队身份。请去掉 `--team`,或改用 `--personal`。 |
| `The --team option is not supported by a self-hosted connector.`(退出码 2) | 自部署运行时没有团队身份。请去掉 `--team`。 |
| 某命令需要 OOMOL 账号 | 非 connector 功能(文件上传、LLM、skills)仍需 `oo auth login`。 |
61 changes: 2 additions & 59 deletions src/application/auth/default-team.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
createSettingsStore,
} from "../../../__tests__/helpers.ts";
import {
clearDefaultTeam,
migrateLegacyDefaultTeam,
readDefaultTeam,
writeDefaultTeam,
Expand Down Expand Up @@ -86,15 +85,15 @@ describe("readDefaultTeam", () => {
});
});

test("resolves to personal when nothing is stored", async () => {
test("resolves to no default team when nothing is stored", async () => {
const { context } = createDefaultTeamContext({
authFile: { auth: [account], id: "user-1" },
});

await expect(readDefaultTeam(context)).resolves.toBeUndefined();
});

test("resolves to personal under OO_API_KEY without touching any store", async () => {
test("resolves to no default team under OO_API_KEY without touching any store", async () => {
const { context } = createDefaultTeamContext({
authStore: createThrowingAuthStore(),
env: { OO_API_KEY: "env-key" },
Expand Down Expand Up @@ -149,62 +148,6 @@ describe("writeDefaultTeam", () => {
});
});

describe("clearDefaultTeam", () => {
test("clears the account default", async () => {
const { context, readAuthFile } = createDefaultTeamContext({
authFile: {
auth: [{ ...account, team: "acme", teamId: "team-1" }],
id: "user-1",
},
});

await expect(clearDefaultTeam(context)).resolves.toBe(true);
expect((await readAuthFile()).auth[0]).toEqual(account);
});

test("clears a legacy value that no account could hold", async () => {
const { context, readSettings } = createDefaultTeamContext({
authFile: { auth: [], id: "" },
settings: { identity: { team: "legacy-team" } },
});

await expect(clearDefaultTeam(context)).resolves.toBe(true);
expect(await readSettings()).toEqual({});
});

test("reports nothing to clear for an account already on the personal identity", async () => {
const { context } = createDefaultTeamContext({
authFile: { auth: [account], id: "user-1" },
});

await expect(clearDefaultTeam(context)).resolves.toBe(false);
});

test("still clears the legacy value when auth.toml cannot be read", async () => {
const { context, readSettings } = createDefaultTeamContext({
authStore: createUnreadableAuthStore(),
settings: { identity: { team: "legacy-team" } },
});

// readDefaultTeam keeps honouring the legacy value in this state, so
// clearing has to reach it too — otherwise the one default still in
// effect is the one that cannot be cleared.
await expect(clearDefaultTeam(context)).resolves.toBe(true);
expect(await readSettings()).toEqual({});
});

test("reports the legacy removal as a migration", async () => {
const { context, recordedProperties } = createDefaultTeamContext({
authFile: { auth: [account], id: "user-1" },
settings: { identity: { team: "legacy-team" } },
});

await clearDefaultTeam(context);

expect(recordedProperties).toEqual([{ team_default_migrated: true }]);
});
});

describe("migrateLegacyDefaultTeam", () => {
test("moves the legacy value onto the active account and deletes it", async () => {
const {
Expand Down
36 changes: 3 additions & 33 deletions src/application/auth/default-team.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import type { CliExecutionContext } from "../contracts/cli.ts";
import type { AppSettings } from "../schemas/settings.ts";

import {
clearAccountDefaultTeam,
getCurrentAuthAccount,
setAccountDefaultTeam,
} from "../schemas/auth.ts";
Expand Down Expand Up @@ -54,8 +53,9 @@ type MigrateDefaultTeamContext = Pick<
>;

/**
* Reads the default team in effect for the active account, or undefined for
* the personal identity.
* Reads the default team in effect for the active account, or undefined when
* none is saved (team-aware commands then send no team header and the gateway
* applies the server-side default team).
*
* With OO_API_KEY set there is no persisted default at all: the credential
* may belong to an entirely different account, and lending it a saved
Expand Down Expand Up @@ -128,36 +128,6 @@ export async function writeDefaultTeam(
return true;
}

/**
* Clears the default team. The legacy value goes too: leaving it behind would
* let the next read resurrect the team this call just cleared.
*
* Returns whether anything was actually cleared, so the caller can tell
* "cleared" from "already personal" without reading the stores itself.
*
* The auth read is tolerant, unlike the one in `writeDefaultTeam`: clearing is
* the one write here that runs without resolving an identity first, so it is
* what a user with an unreadable auth.toml reaches for — and the legacy value
* `readDefaultTeam` still honours in that state has to stay clearable. An
* unreadable file yields no account, so nothing overwrites it.
*/
export async function clearDefaultTeam(
context: WriteDefaultTeamContext,
): Promise<boolean> {
const { authFile } = await context.authStore.readTolerantState();
const account = getCurrentAuthAccount(authFile);
const nextAuthFile = account === undefined
? authFile
: clearAccountDefaultTeam(authFile, account.id);
const clearedAccountTeam = nextAuthFile !== authFile;

if (clearedAccountTeam) {
await context.authStore.write(nextAuthFile);
}

return await dropLegacyIdentityTeam(context) || clearedAccountTeam;
}

/**
* Moves the legacy global default team onto the active account, then deletes
* it. Runs once per invocation before any command, so the value is gone
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ Arguments:
Options:
--team <team> Report each action's authenticated status under the
given team identity
--personal Report authenticated status under your personal
identity, ignoring any configured default team
--format <format> Specify output format (use json for structured output)
--json Alias for --format=json
--show-schema-version Include schemaVersion in JSON output (no effect without
Expand All @@ -77,8 +75,6 @@ Arguments:
Options:
--team <team> Report each action's authenticated status under the
given team identity
--personal Report authenticated status under your personal
identity, ignoring any configured default team
--format <format> Specify output format (use json for structured output)
--json Alias for --format=json
--show-schema-version Include schemaVersion in JSON output (no effect without
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ exports[`auth CLI writes auth-store and auth status logs 1`] = `
"✓ Logged in to oomol.com account Alice
- Active account: true
- API key status: Valid
- Default team: personal (no default team)
- Default team: server default (no saved team)
- Accounts:
* Alice [active] (oomol.com)
"
Expand Down Expand Up @@ -168,7 +168,7 @@ exports[`auth CLI supports auth status for valid and invalid api keys 1`] = `
"X Logged in to oomol.com account Alice
- Active account: true
- API key status: Invalid
- Default team: personal (no default team)
- Default team: server default (no saved team)
- Accounts:
* Alice [active] (oomol.com)
"
Expand All @@ -181,7 +181,7 @@ exports[`auth CLI supports auth status for valid and invalid api keys 1`] = `
"✓ Logged in to oomol.com account Alice
- Active account: true
- API key status: Valid
- Default team: personal (no default team)
- Default team: server default (no saved team)
- Accounts:
* Alice [active] (oomol.com)
"
Expand Down
4 changes: 2 additions & 2 deletions src/application/commands/auth/index.cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2782,7 +2782,7 @@ describe("auth CLI status default team", () => {
}
});

test("reports the personal identity under OO_API_KEY despite a saved default", async () => {
test("reports the server default team under OO_API_KEY despite a saved default", async () => {
const sandbox = await createCliSandbox();

sandbox.env.OO_API_KEY = "env-key-1";
Expand All @@ -2807,7 +2807,7 @@ describe("auth CLI status default team", () => {
// OO_TEAM_NAME.
expect(textResult.exitCode).toBe(0);
expect(textResult.stdout).toContain(
"- Default team: personal (no default team)",
"- Default team: server default (no saved team)",
);
expect(payload.status).toBe("logged-in");
expect(payload.envOverride).toBeDefined();
Expand Down
4 changes: 2 additions & 2 deletions src/application/commands/auth/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ function writeSelfHostedConnectorText(

// Renders the default-team detail row. The value spells out the identity in
// effect: the account default by name, an env override with the variable that
// supplies it, or the personal fallback when no default team is set.
// supplies it, or the server-side default when no default team is saved.
function formatStatusTeamDetail(
context: CliExecutionContext,
teamIdentity: TeamIdentity | undefined,
Expand All @@ -354,7 +354,7 @@ function formatStatusTeamDetail(
if (teamIdentity === undefined) {
return {
label,
value: context.translator.t("auth.status.teamPersonal"),
value: context.translator.t("auth.status.teamServerDefault"),
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,6 @@ Arguments:
Options:
--team <team> Report each action's authenticated status under the
given team identity
--personal Report authenticated status under your personal
identity, ignoring any configured default team
--format <format> Specify output format (use json for structured output)
--json Alias for --format=json
--show-schema-version Include schemaVersion in JSON output (no effect without
Expand All @@ -91,8 +89,6 @@ Arguments:
Options:
--team <team> Report each action's authenticated status under the
given team identity
--personal Report authenticated status under your personal
identity, ignoring any configured default team
--format <format> Specify output format (use json for structured output)
--json Alias for --format=json
--show-schema-version Include schemaVersion in JSON output (no effect without
Expand Down
9 changes: 2 additions & 7 deletions src/application/commands/connector/apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { createWriterColors } from "../../terminal-colors.ts";
import { formatTextTable } from "../shared/text-table.ts";
import {
teamIdentityInputShape,
teamIdentityOptions,
teamOption,
} from "../team/identity.ts";
import { connectorSearchServiceColor } from "./search-provider.ts";
import { resolveConnectorSession } from "./session.ts";
Expand All @@ -35,7 +35,6 @@ type ConnectorAppsListScope = "all" | "service";

interface ConnectorAppsInput {
team?: string;
personal?: boolean;
serviceName?: string;
}

Expand All @@ -62,10 +61,7 @@ export const connectorAppsCommand: CliCommandDefinition<ConnectorAppsInput> = {
},
],
options: [
...teamIdentityOptions({
personal: "options.connectorAppsPersonal",
team: "options.connectorAppsTeam",
}),
teamOption("options.connectorAppsTeam"),
],
output: "standard",
inputSchema: z.object({
Expand All @@ -79,7 +75,6 @@ export const connectorAppsCommand: CliCommandDefinition<ConnectorAppsInput> = {

const { identity, target } = await resolveConnectorSession(
{
personal: input.personal,
team: input.team,
},
context,
Expand Down
Loading