@cydm/happy-elves 0.1.0-beta.294 → 0.1.0-beta.296

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.
@@ -123,12 +123,15 @@ session stars, recent sessions, pinned workspaces, and current project defaults.
123
123
  happy-elves release profile show [--profile <name>] --json
124
124
  happy-elves release profile set <name> --release-tag <tag> [--release-tag <tag>] [--reviewer-tag <tag>] [--dogfood-tag <tag>] [--no-daemon-update] --json
125
125
  happy-elves release resolve [--profile happy-elves] --json
126
+ happy-elves release notify [--profile happy-elves] [--text 发布] [--detach] [--timeout 1000s] --json
126
127
  happy-elves release start [--profile happy-elves] [--expected-commit <sha>] [--target-machine <machineId>] [--no-daemon-update] [--wait] --json
127
128
  happy-elves release status [jobId] [--machine <machineId>] --json
128
129
  happy-elves release logs [jobId] [--machine <machineId>] [--tail 200] --json
129
130
 
130
131
  Release profiles resolve target sessions by encrypted session tags. The built-in
131
132
  happy-elves profile uses Happy Elves + Release Session for the release target.
133
+ Release notify sends the short release prompt to that target with approve-all
134
+ permissions and memory off, so agents do not have to reconstruct release flags.
132
135
  Release start runs a fixed daemon-local runner; it does not accept arbitrary shell.
133
136
  `,
134
137
  gateway: `Usage:
@@ -258,6 +261,7 @@ Core commands:
258
261
  workspace pin-cwd --machine <machineId> --cwd <cwd> --json
259
262
 
260
263
  release resolve --profile happy-elves --json
264
+ release notify --profile happy-elves --json
261
265
  release start --profile happy-elves --json
262
266
 
263
267
  gateway status --json
@@ -320,9 +324,11 @@ Release / reviewer / dogfood workflow:
320
324
  session tag add <sessionId> --tag "Release Session" --json
321
325
  2. Resolve before acting:
322
326
  release resolve --profile happy-elves --json
323
- 3. Start release only when the release target is exactly one session:
327
+ 3. Notify release with the standard short prompt and release permissions:
328
+ release notify --profile happy-elves --json
329
+ 4. Start the structured release runner only when explicitly needed:
324
330
  release start --profile happy-elves --json
325
- 4. Use hidden sessions for temporary dogfood:
331
+ 5. Use hidden sessions for temporary dogfood:
326
332
  session create --hidden --source dogfood --tag "Happy Elves" --tag "Dogfood" ...
327
333
 
328
334
  Concepts:
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { normalizeSessionTags } from "../../../../packages/shared/dist/index.js";
4
- import { CliError, ControllerClient, compactText, ok, parseDurationMs, readConfig, releaseProfilesPath, requirePositional, wantsJson, } from "./lib/index.js";
4
+ import { CliError, ControllerClient, DEFAULT_COMMAND_TIMEOUT_MS, compactText, ok, parseDurationMs, readConfig, releaseProfilesPath, requirePositional, wantsJson, } from "./lib/index.js";
5
5
  const defaultReleaseProfile = {
6
6
  name: "happy-elves",
7
7
  releaseSessionTags: ["Happy Elves", "Release Session"],
@@ -81,6 +81,33 @@ export async function handleRelease({ domain, action, positional, flags }) {
81
81
  }
82
82
  return true;
83
83
  }
84
+ if (action === "notify") {
85
+ const config = await readConfig(flags);
86
+ const client = new ControllerClient(config);
87
+ const { profile } = await loadProfile(profileNameFromFlags(flags));
88
+ const releaseTarget = await resolveRequiredReleaseTarget(client, profile);
89
+ const text = releaseNotifyText(flags);
90
+ const wait = flags.detach !== true && flags["no-wait"] !== true;
91
+ const result = await client.run(releaseTarget.sessionId, text, {
92
+ wait,
93
+ permissionMode: "approve-all",
94
+ memoryMode: "off",
95
+ memoryProjectKey: `${releaseTarget.machineId}:${releaseTarget.cwd}`,
96
+ waitTimeoutMs: parseDurationMs(flags.timeout, DEFAULT_COMMAND_TIMEOUT_MS),
97
+ ackTimeoutMs: parseDurationMs(flags["ack-timeout"], 15_000),
98
+ inactivityTimeoutMs: parseDurationMs(flags["inactivity-timeout"] ?? flags["runtime-timeout"], DEFAULT_COMMAND_TIMEOUT_MS),
99
+ });
100
+ printReleaseOutput("release.notify", {
101
+ profile: profile.name,
102
+ releaseTarget,
103
+ prompt: text,
104
+ permissionMode: "approve-all",
105
+ memoryMode: "off",
106
+ wait,
107
+ ...result,
108
+ }, flags);
109
+ return true;
110
+ }
84
111
  if (action === "status" || action === "logs") {
85
112
  const jobId = positional[0];
86
113
  const config = await readConfig(flags);
@@ -101,6 +128,10 @@ export async function handleRelease({ domain, action, positional, flags }) {
101
128
  function profileNameFromFlags(flags) {
102
129
  return typeof flags.profile === "string" && flags.profile.trim() ? flags.profile.trim() : defaultReleaseProfile.name;
103
130
  }
131
+ function releaseNotifyText(flags) {
132
+ const value = typeof flags.text === "string" ? flags.text.trim() : "";
133
+ return value || "发布";
134
+ }
104
135
  function profileFromFlags(name, flags) {
105
136
  const releaseSessionTags = tagsFromFlags(flags, "release-tag", "release-tags");
106
137
  if (releaseSessionTags.length === 0)
@@ -212,6 +243,16 @@ async function resolveTaggedSession(client, tags) {
212
243
  return { kind: "ambiguous", candidates: summaries };
213
244
  return { kind: "found", session: summaries[0] };
214
245
  }
246
+ async function resolveRequiredReleaseTarget(client, profile) {
247
+ const release = await resolveTaggedSession(client, profile.releaseSessionTags);
248
+ if (release.kind === "missing") {
249
+ throw new CliError(`No release session matches tags: ${profile.releaseSessionTags.join(", ")}`, "TAG_TARGET_NOT_FOUND");
250
+ }
251
+ if (release.kind === "ambiguous") {
252
+ throw new CliError(`Multiple release sessions match tags: ${profile.releaseSessionTags.join(", ")}`, "TAG_TARGET_AMBIGUOUS");
253
+ }
254
+ return release.session;
255
+ }
215
256
  function emptyReleaseTarget() {
216
257
  return { sessionId: "", machineId: "", agent: "", cwd: "", name: "", tags: [] };
217
258
  }
@@ -203,6 +203,17 @@ function metadataTags(metadata) {
203
203
  function isRecord(value) {
204
204
  return typeof value === "object" && value !== null && !Array.isArray(value);
205
205
  }
206
+ function sessionHiddenFromDefaultList(metadata) {
207
+ return metadata?.hiddenFromWorkspace === true ||
208
+ metadata?.visibility === "hidden" ||
209
+ metadata?.source === "dogfood";
210
+ }
211
+ async function defaultListIncludesSession(client, session) {
212
+ if (session.status === "closed")
213
+ return false;
214
+ const metadata = await client.decodeSessionMetadata(session);
215
+ return !sessionHiddenFromDefaultList(metadata);
216
+ }
206
217
  function diagnosticSessionMetadata(metadata) {
207
218
  const runtime = isRecord(metadata.runtime) ? metadata.runtime : {};
208
219
  return {
@@ -428,7 +439,9 @@ export async function handleSession({ domain, action, positional, flags }) {
428
439
  name: typeof flags.name === "string" ? flags.name : undefined,
429
440
  ...(tags.length > 0 ? { tags } : {}),
430
441
  });
431
- const visibleSessions = flags.all === true ? allSessions : allSessions.filter((session) => session.status !== "closed");
442
+ const visibleSessions = flags.all === true
443
+ ? allSessions
444
+ : (await Promise.all(allSessions.map(async (session) => (await defaultListIncludesSession(client, session) ? session : undefined)))).filter((session) => Boolean(session));
432
445
  const { items: sessions, page } = selectWindow(visibleSessions, flags, 20);
433
446
  if (wantsJson(flags) || wantsVerbose(flags)) {
434
447
  const machinesById = new Map();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves-cli",
3
- "version": "0.1.0-beta.294",
3
+ "version": "0.1.0-beta.296",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves-daemon",
3
- "version": "0.1.0-beta.294",
3
+ "version": "0.1.0-beta.296",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves",
3
- "version": "0.1.0-beta.294",
3
+ "version": "0.1.0-beta.296",
4
4
  "description": "Remote controller for local coding agents with hosted or self-hosted relay support.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -563,9 +563,11 @@ export class ControllerClient {
563
563
  importRequestId: requestId,
564
564
  name,
565
565
  ...(input.providerName ? { providerName: input.providerName } : {}),
566
+ ...(input.hiddenFromWorkspace ? { hiddenFromWorkspace: true, visibility: "hidden" } : {}),
566
567
  importedFromRuntimeSessionId: runtimeSessionId,
567
568
  importedRuntimeState: input.runtimeState,
568
569
  importedSummary: input.summary,
570
+ ...(input.source ? { source: input.source } : {}),
569
571
  }),
570
572
  });
571
573
  if (result.sessionId) {
@@ -366,8 +366,10 @@ export type ImportHistoricalSessionInput = {
366
366
  cwd: string;
367
367
  runtimeSessionId: string;
368
368
  name: string;
369
+ hiddenFromWorkspace?: boolean;
369
370
  providerName?: string;
370
371
  runtimeState?: HistoricalSessionSnapshot["runtimeState"];
372
+ source?: string;
371
373
  summary?: string;
372
374
  };
373
375
  export type ForkInput = {