@yagni-app/code-staging 0.1.0-staging.997.1 → 0.2.0-staging.1025.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +58 -9
  2. package/dist/claudeCompat.d.ts +36 -5
  3. package/dist/claudeCompat.js +85 -23
  4. package/dist/claudePlugins.d.ts +109 -0
  5. package/dist/claudePlugins.js +336 -0
  6. package/dist/cli.js +14 -4
  7. package/dist/crashReport.d.ts +135 -0
  8. package/dist/crashReport.js +291 -0
  9. package/dist/doctor.d.ts +21 -0
  10. package/dist/doctor.js +52 -0
  11. package/dist/extension/askAdvisorTool.js +7 -1
  12. package/dist/extension/bless.js +16 -3
  13. package/dist/extension/boostCommand.d.ts +144 -0
  14. package/dist/extension/boostCommand.js +263 -0
  15. package/dist/extension/branding.d.ts +31 -0
  16. package/dist/extension/branding.js +37 -0
  17. package/dist/extension/chipEditor.js +7 -3
  18. package/dist/extension/claudeRules.d.ts +54 -0
  19. package/dist/extension/claudeRules.js +180 -0
  20. package/dist/extension/config.d.ts +61 -0
  21. package/dist/extension/config.js +86 -0
  22. package/dist/extension/costHud.d.ts +128 -15
  23. package/dist/extension/costHud.js +189 -19
  24. package/dist/extension/crashReport.d.ts +89 -0
  25. package/dist/extension/crashReport.js +241 -0
  26. package/dist/extension/index.d.ts +43 -4
  27. package/dist/extension/index.js +241 -32
  28. package/dist/extension/initPass.d.ts +65 -47
  29. package/dist/extension/initPass.js +145 -145
  30. package/dist/extension/mcpTools.d.ts +57 -0
  31. package/dist/extension/mcpTools.js +132 -0
  32. package/dist/extension/pipeline/eval.d.ts +42 -5
  33. package/dist/extension/pipeline/eval.js +44 -0
  34. package/dist/extension/pipeline/goCommand.d.ts +18 -0
  35. package/dist/extension/pipeline/goCommand.js +139 -26
  36. package/dist/extension/pipeline/goCompareCommand.d.ts +18 -8
  37. package/dist/extension/pipeline/goCompareCommand.js +42 -23
  38. package/dist/extension/pipeline/orchestrator.js +9 -0
  39. package/dist/extension/pipeline/runCostTable.d.ts +37 -0
  40. package/dist/extension/pipeline/runCostTable.js +165 -0
  41. package/dist/extension/pipeline/runState.d.ts +19 -0
  42. package/dist/extension/pipeline/runState.js +11 -0
  43. package/dist/extension/pipeline/runner.d.ts +19 -0
  44. package/dist/extension/pipeline/runner.js +13 -1
  45. package/dist/extension/pipeline/scrubSecrets.js +2 -2
  46. package/dist/extension/pipeline/stages.d.ts +3 -1
  47. package/dist/extension/pipeline/stages.js +3 -1
  48. package/dist/extension/pipeline/types.d.ts +7 -4
  49. package/dist/extension/pipeline/verify.js +6 -1
  50. package/dist/extension/pipeline/worktree.js +3 -1
  51. package/dist/extension/provider.d.ts +7 -1
  52. package/dist/extension/provider.js +8 -1
  53. package/dist/extension/recall.js +5 -2
  54. package/dist/extension/rerouteNotice.d.ts +42 -0
  55. package/dist/extension/rerouteNotice.js +67 -0
  56. package/dist/extension/sessionRuns.d.ts +45 -0
  57. package/dist/extension/sessionRuns.js +77 -0
  58. package/dist/extension/subagents.d.ts +17 -7
  59. package/dist/extension/subagents.js +52 -7
  60. package/dist/launch.d.ts +17 -3
  61. package/dist/launch.js +22 -9
  62. package/dist/login.d.ts +7 -0
  63. package/dist/login.js +3 -1
  64. package/package.json +2 -2
@@ -0,0 +1,77 @@
1
+ /**
2
+ * YAG-383: tracks the `/go` run ids a driver session has launched, so /cost's
3
+ * server-authoritative spend fetch can widen its query to include them.
4
+ *
5
+ * The backend writes a run-attributed dispatch's `llm_usage.session_id` as
6
+ * the RUN id, not the driver session id (see attributionHeaders in
7
+ * config.ts: a `/go` child's completions carry `x-yagni-run-id`, and the
8
+ * proxy stores THAT in `session_id` — the run id wins, by design, so a
9
+ * per-run query works on its own). A plain `GET /api/yagni-code/spend?
10
+ * sessionId=<driver>` therefore never sees a single `/go` run's spend: it is
11
+ * real spend, billed under this session's work, but invisible to that query.
12
+ * `/cost` (index.ts's `fetchSpend`) closes that gap by also asking for every
13
+ * run id this process has launched, via `runIds=`.
14
+ *
15
+ * Module-level state is the right shape here: one CLI process IS one driver
16
+ * session, so "run ids this process has launched" and "run ids this session
17
+ * has launched" are the same list. Capped at the most recent
18
+ * `MAX_TRACKED_RUN_IDS` so an extremely long-lived session's /cost call never
19
+ * grows an unbounded query string (the backend independently caps `runIds` at
20
+ * the same number — see MAX_SPEND_RUN_IDS in routes/yagniCode.ts — capping
21
+ * symmetrically here keeps a stable, useful most-recent set instead of
22
+ * trailing off into a 400 from the server).
23
+ */
24
+ const MAX_TRACKED_RUN_IDS = 20;
25
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
26
+ let runIds = [];
27
+ /**
28
+ * Count of run ids ever pushed off the front of `runIds` by the
29
+ * `MAX_TRACKED_RUN_IDS` cap below. Carry-over from the /cost re-review: a
30
+ * long-lived session that launches more than 20 `/go` runs silently lost the
31
+ * oldest ones from /cost's `runIds=` widening, with no indication that
32
+ * anything was left out — an honesty gap identical in spirit to
33
+ * `counterfactualIncomplete` / `unbilledSellMillicents` elsewhere in this
34
+ * surface. Tracked here (not derived) because the dropped ids themselves are
35
+ * gone — only the count survives.
36
+ */
37
+ let dropped = 0;
38
+ /**
39
+ * Record a `/go` run id once it is known (the goCommand.ts seam right after
40
+ * `runSession.start()` resolves). Ignores anything that is not UUID-shaped
41
+ * and de-dupes a repeat call for the same run (a resumed/re-adopted run can
42
+ * report the same id twice) — both defensive, since a malformed value here
43
+ * must never corrupt /cost's query string.
44
+ */
45
+ export function recordSessionRun(runId) {
46
+ if (!UUID_RE.test(runId))
47
+ return;
48
+ if (runIds.includes(runId))
49
+ return;
50
+ runIds.push(runId);
51
+ if (runIds.length > MAX_TRACKED_RUN_IDS) {
52
+ const overflow = runIds.length - MAX_TRACKED_RUN_IDS;
53
+ dropped += overflow;
54
+ runIds = runIds.slice(overflow);
55
+ }
56
+ }
57
+ /** The run ids recorded so far this process, oldest first, capped. */
58
+ export function sessionRunIds() {
59
+ return [...runIds];
60
+ }
61
+ /**
62
+ * How many run ids this session has ever dropped off the front of the
63
+ * `MAX_TRACKED_RUN_IDS` cap. Zero for the overwhelming majority of sessions
64
+ * (20 `/go` runs in one process is a lot); /cost's server rendering
65
+ * (costHud.ts's `formatServerCostLines`) surfaces this as an "Excludes N
66
+ * earlier /go runs." note when it is nonzero, so a long session's spend total
67
+ * is never silently missing runs with no explanation.
68
+ */
69
+ export function droppedSessionRuns() {
70
+ return dropped;
71
+ }
72
+ /** Test-only reset: module-level state otherwise leaks across test files/cases. */
73
+ export function _resetSessionRunsForTest() {
74
+ runIds = [];
75
+ dropped = 0;
76
+ }
77
+ //# sourceMappingURL=sessionRuns.js.map
@@ -9,9 +9,11 @@
9
9
  *
10
10
  * Agent definitions are the Claude Code format — markdown with
11
11
  * name/description/tools/model frontmatter — discovered from the repo's
12
- * `.claude/agents/` and `.pi/agents/` plus the user's `~/.claude/agents/`
13
- * (project beats user on a name collision). A built-in `general` agent is
14
- * always available, so the tool works in a repo with no definitions at all.
12
+ * `.claude/agents/` and `.pi/agents/`, the user's `~/.claude/agents/`, and
13
+ * any Claude Code plugin `agents/` dirs the launcher passed via
14
+ * `YAGNI_CLAUDE_AGENT_DIRS` (project beats user beats plugin on a name
15
+ * collision). A built-in `general` agent is always available, so the tool
16
+ * works in a repo with no definitions at all.
15
17
  * Claude model names map onto YAGNI tiers (opus→advanced, sonnet→standard,
16
18
  * haiku→efficient); unknown models run on standard.
17
19
  */
@@ -28,7 +30,12 @@ export declare const MAX_PARALLEL_SUBAGENTS = 4;
28
30
  * subagent would inherit.
29
31
  */
30
32
  export declare const DEFAULT_SUBAGENT_TOOLS: string[];
31
- export type SubagentSource = "project-claude" | "project-pi" | "user-claude" | "builtin";
33
+ export type SubagentSource = "project-claude" | "project-pi" | "user-claude" | "plugin" | "builtin";
34
+ /**
35
+ * Claude Code plugin `agents/` dirs, delimiter-joined by the yagni launcher
36
+ * (see yagni-code-cli claudeCompat.ts — trust gating happens there).
37
+ */
38
+ export declare const CLAUDE_AGENT_DIRS_ENV = "YAGNI_CLAUDE_AGENT_DIRS";
32
39
  export interface SubagentDef {
33
40
  name: string;
34
41
  description: string;
@@ -49,11 +56,14 @@ export declare function parseAgentMarkdown(content: string, fallbackName: string
49
56
  export interface DiscoverDeps {
50
57
  cwd: string;
51
58
  homeDir?: string;
59
+ /** Env seam for the plugin agent dirs (defaults to process.env). */
60
+ env?: NodeJS.ProcessEnv;
52
61
  }
53
62
  /**
54
- * All available agents: built-in general, then user `~/.claude/agents`, then
55
- * the repo's `.pi/agents` and `.claude/agents` — later wins on a name
56
- * collision, so a project definition beats a user one beats the built-in.
63
+ * All available agents: built-in general, then Claude Code plugin `agents/`
64
+ * dirs (launcher-provided), then user `~/.claude/agents`, then the repo's
65
+ * `.pi/agents` and `.claude/agents` — later wins on a name collision, so a
66
+ * project definition beats a user one beats a plugin one beats the built-in.
57
67
  */
58
68
  export declare function discoverSubagents(deps: DiscoverDeps): SubagentDef[];
59
69
  export declare function formatAgentList(agents: SubagentDef[]): string;
@@ -9,18 +9,27 @@
9
9
  *
10
10
  * Agent definitions are the Claude Code format — markdown with
11
11
  * name/description/tools/model frontmatter — discovered from the repo's
12
- * `.claude/agents/` and `.pi/agents/` plus the user's `~/.claude/agents/`
13
- * (project beats user on a name collision). A built-in `general` agent is
14
- * always available, so the tool works in a repo with no definitions at all.
12
+ * `.claude/agents/` and `.pi/agents/`, the user's `~/.claude/agents/`, and
13
+ * any Claude Code plugin `agents/` dirs the launcher passed via
14
+ * `YAGNI_CLAUDE_AGENT_DIRS` (project beats user beats plugin on a name
15
+ * collision). A built-in `general` agent is always available, so the tool
16
+ * works in a repo with no definitions at all.
15
17
  * Claude model names map onto YAGNI tiers (opus→advanced, sonnet→standard,
16
18
  * haiku→efficient); unknown models run on standard.
17
19
  */
18
20
  import * as fs from "node:fs";
19
21
  import { homedir } from "node:os";
20
- import { join } from "node:path";
22
+ import { delimiter, join } from "node:path";
21
23
  import { parseFrontmatter } from "@earendil-works/pi-coding-agent";
22
24
  import { Type } from "typebox";
25
+ import { sanitizeCallerSegment } from "./config.js";
23
26
  import { runStage } from "./pipeline/runner.js";
27
+ /**
28
+ * YAG-471 attribution: the `x-yagni-caller` prefix for a subagent invocation.
29
+ * The sanitized agent name is capped so the WHOLE label (prefix + name) stays
30
+ * within the model proxy's 64-char caller-label limit.
31
+ */
32
+ const SUBAGENT_CALLER_PREFIX = "subagent:";
24
33
  export const SUBAGENT_TOOL_NAME = "subagent";
25
34
  export const GENERAL_AGENT_NAME = "general";
26
35
  export const MAX_PARALLEL_SUBAGENTS = 4;
@@ -39,6 +48,21 @@ export const DEFAULT_SUBAGENT_TOOLS = [
39
48
  "write",
40
49
  "ask_yagni",
41
50
  ];
51
+ /**
52
+ * Claude Code plugin `agents/` dirs, delimiter-joined by the yagni launcher
53
+ * (see yagni-code-cli claudeCompat.ts — trust gating happens there).
54
+ */
55
+ export const CLAUDE_AGENT_DIRS_ENV = "YAGNI_CLAUDE_AGENT_DIRS";
56
+ const CLAUDE_COMPAT_DISABLE_ENV = "YAGNI_DISABLE_CLAUDE_COMPAT";
57
+ function pluginAgentDirs(env) {
58
+ const disabled = env[CLAUDE_COMPAT_DISABLE_ENV];
59
+ if (disabled !== undefined && disabled !== "" && disabled !== "0")
60
+ return [];
61
+ const raw = env[CLAUDE_AGENT_DIRS_ENV];
62
+ if (!raw)
63
+ return [];
64
+ return raw.split(delimiter).filter(Boolean);
65
+ }
42
66
  const GENERAL_BODY = `You are a capable software-engineering subagent with a fresh context window. Complete the task you are given end to end, autonomously.
43
67
 
44
68
  You are grounded in how THIS company works: call ask_yagni before inferring a convention, an ownership rule, or anything organization-specific.
@@ -51,6 +75,10 @@ const GENERAL_AGENT = {
51
75
  body: GENERAL_BODY,
52
76
  source: "builtin",
53
77
  };
78
+ // Concrete tiers a subagent can actually run on. `balanced` is deliberately NOT
79
+ // a member here even though it is a member of `ModelTier`: a subagent needs
80
+ // ONE model for its whole run, and balanced is a session-level routing policy,
81
+ // not a rung — see mapModelTier's explicit branch below.
54
82
  const TIER_VALUES = ["peak", "advanced", "standard", "efficient"];
55
83
  /** Claude Code model names → YAGNI tiers; YAGNI tiers pass through. */
56
84
  export function mapModelTier(model) {
@@ -59,6 +87,15 @@ export function mapModelTier(model) {
59
87
  const normalized = model.trim().toLowerCase();
60
88
  if (TIER_VALUES.includes(normalized))
61
89
  return normalized;
90
+ // `balanced` names a SESSION routing policy, not a tier a subagent can run
91
+ // on. Handled explicitly rather than falling through to the catch-all
92
+ // below, so frontmatter that names the policy reads as an intentional
93
+ // "router-decide" choice, not an unrecognized string: it lands on the
94
+ // built-in default lane (standard) — the session-level policy (driver
95
+ // turns) owns balanced's resolution; this is just a safe landing spot for
96
+ // frontmatter that names it.
97
+ if (normalized === "balanced")
98
+ return "standard";
62
99
  if (normalized.includes("opus"))
63
100
  return "advanced";
64
101
  if (normalized.includes("sonnet"))
@@ -117,14 +154,16 @@ function loadAgentsFromDir(dir, source) {
117
154
  return agents;
118
155
  }
119
156
  /**
120
- * All available agents: built-in general, then user `~/.claude/agents`, then
121
- * the repo's `.pi/agents` and `.claude/agents` — later wins on a name
122
- * collision, so a project definition beats a user one beats the built-in.
157
+ * All available agents: built-in general, then Claude Code plugin `agents/`
158
+ * dirs (launcher-provided), then user `~/.claude/agents`, then the repo's
159
+ * `.pi/agents` and `.claude/agents` — later wins on a name collision, so a
160
+ * project definition beats a user one beats a plugin one beats the built-in.
123
161
  */
124
162
  export function discoverSubagents(deps) {
125
163
  const home = deps.homeDir ?? homedir();
126
164
  const layers = [
127
165
  [GENERAL_AGENT],
166
+ ...pluginAgentDirs(deps.env ?? process.env).map((dir) => loadAgentsFromDir(dir, "plugin")),
128
167
  loadAgentsFromDir(join(home, ".claude", "agents"), "user-claude"),
129
168
  loadAgentsFromDir(join(deps.cwd, ".pi", "agents"), "project-pi"),
130
169
  loadAgentsFromDir(join(deps.cwd, ".claude", "agents"), "project-claude"),
@@ -138,6 +177,7 @@ export function discoverSubagents(deps) {
138
177
  }
139
178
  const SOURCE_LABEL = {
140
179
  builtin: "built-in",
180
+ plugin: "claude plugin",
141
181
  "user-claude": "~/.claude/agents",
142
182
  "project-pi": ".pi/agents",
143
183
  "project-claude": ".claude/agents",
@@ -234,6 +274,11 @@ export function makeSubagentTool(deps = {}) {
234
274
  cwd,
235
275
  signal,
236
276
  personaBody: () => def.body,
277
+ // YAG-471: attribute this child's completions to the specific
278
+ // subagent, not the generic /go stage label the runner would
279
+ // otherwise derive from stage.id ("implement", reused as the
280
+ // synthetic subagent stage — see buildSubagentStage).
281
+ callerLabel: `${SUBAGENT_CALLER_PREFIX}${sanitizeCallerSegment(def.name, 64 - SUBAGENT_CALLER_PREFIX.length)}`,
237
282
  });
238
283
  return { agent: def.name, task, result };
239
284
  }));
package/dist/launch.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  /**
2
- * Build the configured pi launch. `buildLaunch` is pure (no spawn, no fs) so it
3
- * is fully unit-testable: it returns the child environment and the pi argv that
4
- * registers pi-extension-yagni and defaults the provider to `yagni`.
2
+ * Build the configured pi launch. `buildLaunch` has no spawn/fs side effects,
3
+ * so it stays fully unit-testable, though it is no longer strictly pure: it
4
+ * mints a fresh session id (`randomUUID()`) on every call. It returns the
5
+ * child environment and the pi argv that registers pi-extension-yagni and
6
+ * defaults the provider to `yagni`.
5
7
  */
6
8
  import type { Credentials } from "./credentials.js";
7
9
  export interface LaunchPlan {
@@ -61,6 +63,12 @@ export interface BuildLaunchOptions {
61
63
  profilePath?: string;
62
64
  /** Distribution-specific root shared with the bundled extension. */
63
65
  stateDir?: string;
66
+ /**
67
+ * Launcher version, forwarded as `YAGNI_CODE_VERSION` so the bundled
68
+ * extension (which has no package.json on disk to read) can label its crash
69
+ * reports with the shipping version. Optional: omitted, reports say "unknown".
70
+ */
71
+ cliVersion?: string;
64
72
  /** Base environment to extend (defaults to process.env at call sites). */
65
73
  baseEnv?: NodeJS.ProcessEnv;
66
74
  /** Clock seam for the token-expiry preflight (defaults to Date.now). */
@@ -71,6 +79,12 @@ export interface BuildLaunchOptions {
71
79
  * anything the user passes explicitly still wins.
72
80
  */
73
81
  extraAgentArgs?: string[];
82
+ /**
83
+ * Launcher-computed child env (e.g. Claude-compat plugin agent dirs and
84
+ * rules dirs). Merged before the YAGNI-critical keys so it can never
85
+ * override the token, base URL, or hermetic agent dir.
86
+ */
87
+ extraEnv?: Record<string, string>;
74
88
  }
75
89
  export declare function buildLaunch(creds: Credentials | null, passthroughArgs: string[], opts: BuildLaunchOptions): LaunchPlan;
76
90
  //# sourceMappingURL=launch.d.ts.map
package/dist/launch.js CHANGED
@@ -1,8 +1,11 @@
1
1
  /**
2
- * Build the configured pi launch. `buildLaunch` is pure (no spawn, no fs) so it
3
- * is fully unit-testable: it returns the child environment and the pi argv that
4
- * registers pi-extension-yagni and defaults the provider to `yagni`.
2
+ * Build the configured pi launch. `buildLaunch` has no spawn/fs side effects,
3
+ * so it stays fully unit-testable, though it is no longer strictly pure: it
4
+ * mints a fresh session id (`randomUUID()`) on every call. It returns the
5
+ * child environment and the pi argv that registers pi-extension-yagni and
6
+ * defaults the provider to `yagni`.
5
7
  */
8
+ import { randomUUID } from "node:crypto";
6
9
  import { agentDirEnvVar } from "./branding.js";
7
10
  /**
8
11
  * How close to expiry the token can be before launch warns. A coding session
@@ -50,6 +53,13 @@ export function buildLaunch(creds, passthroughArgs, opts) {
50
53
  }
51
54
  const env = {
52
55
  ...(opts.baseEnv ?? {}),
56
+ ...(opts.extraEnv ?? {}),
57
+ // One session id per launch (YAG-471 attribution). The extension reads this
58
+ // as YAGNI_SESSION_ID and forwards it as the model proxy's x-yagni-session-id
59
+ // header on the driver's own completions. /go's children override
60
+ // YAGNI_CALLER with their own stage/subagent/advisor label but inherit this
61
+ // session id (unless they also carry a YAGNI_RUN_ID, which wins server-side).
62
+ YAGNI_SESSION_ID: randomUUID(),
53
63
  YAGNI_TOKEN: creds.token,
54
64
  YAGNI_BASE_URL: creds.baseUrl,
55
65
  // Forward the token's expiry (when known) so the extension can surface a
@@ -63,6 +73,9 @@ export function buildLaunch(creds, passthroughArgs, opts) {
63
73
  // the launcher read (0600). Absent when the caller can't resolve it.
64
74
  ...(opts.profilePath ? { YAGNI_PROFILE_PATH: opts.profilePath } : {}),
65
75
  ...(opts.stateDir ? { YAGNI_CODE_HOME: opts.stateDir } : {}),
76
+ // Forward the launcher's version so the extension's crash reports carry
77
+ // the shipping version (the bundled extension has no package.json to read).
78
+ ...(opts.cliVersion ? { YAGNI_CODE_VERSION: opts.cliVersion } : {}),
66
79
  // Rebrand pi's chrome ("pi"/"π" → "YAGNI Code"): pi reads piConfig.name from
67
80
  // the package at PI_PACKAGE_DIR (a shadow package the launcher generates).
68
81
  ...(opts.piPackageDir ? { PI_PACKAGE_DIR: opts.piPackageDir } : {}),
@@ -87,12 +100,12 @@ export function buildLaunch(creds, passthroughArgs, opts) {
87
100
  // explicitly chose one (so power users can still point pi elsewhere).
88
101
  const userChoseProvider = passthroughArgs.some(arg => arg === "--provider" || arg.startsWith("--provider="));
89
102
  // Default the model to the `balanced` tier (YAG-380). Balanced drives on the
90
- // judgment-grade `advanced` model — the same driver quality this default gave
91
- // before — and additionally lets the session escalate hard calls to `peak`
92
- // through the capped ask_advisor tool. So it is strictly additive: no session
93
- // reasons worse than it did, and the hard ones can reason better. A user
94
- // `--model` (e.g. `standard` or `efficient`) still wins. Without this, pi's
95
- // default-model heuristic could land an interactive session on a weaker tier.
103
+ // judgment-grade `advanced` model and additionally lets the session escalate
104
+ // hard calls to `peak` through the capped ask_advisor tool. So it is strictly
105
+ // additive: no session reasons worse than plain advanced, and the hard ones
106
+ // can reason better. A user `--model` (e.g. `standard` or `efficient`) still
107
+ // wins. Without this, pi's default-model heuristic could land an interactive
108
+ // session on a weaker tier.
96
109
  // Detect if the user explicitly set a model, whether via "--model" as a separate
97
110
  // argument or using the equals form "--model=efficient". The previous check only
98
111
  // caught the separate form, causing a duplicate "--model balanced" to be added
package/dist/login.d.ts CHANGED
@@ -9,6 +9,13 @@ export interface LoginDeps {
9
9
  baseUrl?: string;
10
10
  /** Which profile to store the minted token in. Defaults to the active one. */
11
11
  profileName?: string;
12
+ /**
13
+ * Client identifier sent with the token poll so the backend can attribute
14
+ * the minted credential to the app that requested it. Defaults to "cli";
15
+ * the desktop shell (which reuses this flow) passes "desktop". The server
16
+ * treats unknown values as absent, so this can never break a login.
17
+ */
18
+ client?: string;
12
19
  fetchImpl?: typeof fetch;
13
20
  now?: () => number;
14
21
  sleep?: (ms: number) => Promise<void>;
package/dist/login.js CHANGED
@@ -100,7 +100,9 @@ export async function login(deps = {}) {
100
100
  res = await fetchImpl(`${baseUrl}/api/yagni-code/auth/token`, {
101
101
  method: "POST",
102
102
  headers: { "content-type": "application/json" },
103
- body: JSON.stringify({ device_code: start.device_code }),
103
+ // `client` attributes the minted token to the requesting app; servers
104
+ // that predate the field simply ignore it.
105
+ body: JSON.stringify({ device_code: start.device_code, client: deps.client ?? "cli" }),
104
106
  signal: AbortSignal.timeout(POLL_REQUEST_TIMEOUT_MS),
105
107
  });
106
108
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yagni-app/code-staging",
3
- "version": "0.1.0-staging.997.1",
3
+ "version": "0.2.0-staging.1025.1",
4
4
  "description": "YAGNI Code: a terminal coding agent that already knows your company. One YAGNI login routes the model and grounds the agent in your team's context.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "author": "YAGNI, Inc. <jack@yagni.app> (https://yagni.app)",
@@ -38,5 +38,5 @@
38
38
  "@earendil-works/pi-tui": "0.83.0",
39
39
  "typebox": "^1.1.38"
40
40
  },
41
- "yagniSourceSha": "015f793032920ead37a63edc0dc4401fa7b50eec"
41
+ "yagniSourceSha": "ad43200e08a990ead4d891e469d4b88e338b070a"
42
42
  }