aegis-desktop 0.8.6 → 0.8.7

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.
@@ -0,0 +1,89 @@
1
+ # Marketing assets — captions, claim, and tagged links
2
+
3
+ The three PNGs in this directory are the **existing** asset kit (1440×900, shot
4
+ against the real app — see `docs/marketing-log.md`, 2026-09-22 entry). Images
5
+ carry no link of their own, so the outbound link for each placement lives here,
6
+ in the same directory as the image it belongs to. Nothing in this directory is a
7
+ new asset: these are the captions for the three files below, rewritten for the
8
+ Phase 5 promotion pass (`docs/upgrade-conversion-plan.md` §5).
9
+
10
+ **The one claim (Phase 3, verbatim, same sentence as the welcome panel):**
11
+
12
+ > **The autonomous queue keeps running after you close the laptop.**
13
+
14
+ Plus the honesty line that sits next to it:
15
+
16
+ > Everything on your machine is free, permanently. The cloud drain is what you
17
+ > pay for.
18
+
19
+ (`desktop/renderer/app.js` → `PAID_PROMISE` / `FREE_HONESTY`, rendered by
20
+ `#welcome-template` in `desktop/renderer/index.html`.)
21
+
22
+ **What the caption must not be:** a tour of the three model classes, or the free
23
+ local row written up as if it were the product. The screenshots show the picker
24
+ and the diff card — that is *what the image is of*, not the *offer*. One claim
25
+ per asset; the class list stays out of the caption.
26
+
27
+ **Channel rule.** Facebook and Reddit are image-friendly; X and YouTube captions
28
+ follow `docs/launch-copy-x-youtube.md` §2/§3. Every link is
29
+ `https://aegiscloud.org/go/upgrade?s=<channel>&c=<campaign>` with `s` in
30
+ `(x, youtube, reddit, facebook)` — the only channels `SIGNUP_SOURCES` in
31
+ `aegis1/app.py` accepts — and one campaign id per channel, stable across every
32
+ asset on that channel.
33
+
34
+ | Image | Placement | Channel `s` | Campaign `c` | Tagged link (paste this exact URL) |
35
+ |---|---|---|---|---|
36
+ | `01-model-class-picker.png` | X post (picker + claim in the reply) | `x` | `x_launch` | `https://aegiscloud.org/go/upgrade?s=x&c=x_launch` |
37
+ | `01-model-class-picker.png` | Reddit image post (r/LocalLLaMA, r/ollama — link in the closing block, after the limitations) | `reddit` | `reddit_launch` | `https://aegiscloud.org/go/upgrade?s=reddit&c=reddit_launch` |
38
+ | `01-model-class-picker.png` | Facebook Page + groups (image + claim + link) | `facebook` | `facebook_launch` | `https://aegiscloud.org/go/upgrade?s=facebook&c=facebook_launch` |
39
+ | `02-answer-complete.png` | YouTube Short / long-form B-roll; `Upgrade:` line in the description | `youtube` | `yt_launch` | `https://aegiscloud.org/go/upgrade?s=youtube&c=yt_launch` |
40
+ | `02-answer-complete.png` | X post (answer in flight) | `x` | `x_launch` | `https://aegiscloud.org/go/upgrade?s=x&c=x_launch` |
41
+ | `03-tool-approval-diff.png` | X pinned post / thread 3- (the diff card) | `x` | `x_launch` | `https://aegiscloud.org/go/upgrade?s=x&c=x_launch` |
42
+ | `03-tool-approval-diff.png` | Reddit GIF+text post (r/ChatGPTCoding) | `reddit` | `reddit_launch` | `https://aegiscloud.org/go/upgrade?s=reddit&c=reddit_launch` |
43
+ | `03-tool-approval-diff.png` | YouTube thumbnail / description | `youtube` | `yt_launch` | `https://aegiscloud.org/go/upgrade?s=youtube&c=yt_launch` |
44
+
45
+ Every row above is registered in `docs/marketing-log.md` §7 so post-hoc revenue
46
+ can be joined to the channel and the asset face that produced it.
47
+
48
+ ## Captions
49
+
50
+ ### `01-model-class-picker.png` — the class picker
51
+
52
+ > The autonomous queue keeps running after you close the laptop. Everything on
53
+ > your machine is free, permanently — the cloud drain is what you pay for.
54
+ > npm i -g aegis-desktop
55
+ > https://aegiscloud.org/go/upgrade?s=x&c=x_launch
56
+
57
+ Facebook variant (same claim, same tagged shape, different channel):
58
+
59
+ > The autonomous queue keeps running after you close the laptop. Cloud-backed
60
+ > drain, survives a reboot, fans a task out across workers, memory carried
61
+ > between tasks. Everything on your machine is free, permanently.
62
+ > npm i -g aegis-desktop
63
+ > https://aegiscloud.org/go/upgrade?s=facebook&c=facebook_launch
64
+
65
+ Do **not** caption this image with the picker's three rows as the pitch. The
66
+ picker is a fact about the product; the claim above is the offer.
67
+
68
+ ### `02-answer-complete.png` — an answer, complete
69
+
70
+ > The autonomous queue keeps running after you close the laptop — the work does
71
+ > not stop when the laptop does. Everything on your machine is free, permanently.
72
+ > Install: `npm i -g aegis-desktop && aegis`
73
+ > https://aegiscloud.org/go/upgrade?s=youtube&c=yt_launch
74
+
75
+ ### `03-tool-approval-diff.png` — the diff/approval card
76
+
77
+ > The diff card is the whole product locally — and it is free, permanently. The
78
+ > pocket-book item is the drain that keeps running after you close the laptop.
79
+ > Install: `npm i -g aegis-desktop && aegis`
80
+ > https://aegiscloud.org/go/upgrade?s=x&c=x_launch
81
+
82
+ ## Before posting (§0 of `docs/marketing-plan-social.md`)
83
+
84
+ 1. Re-verify the version (`desktop/package.json`) and re-shoot the image if the
85
+ UI moved; log that check in `docs/marketing-log.md` §6.
86
+ 2. Confirm the link still resolves and still carries `s=`/`c=`. A bare
87
+ `aegiscloud.org/subscribe` (bare, untagged) link files as `direct` and the channel
88
+ gets no row.
89
+ 3. Never invent a number in a caption (marketing-log §2 rule 1).
@@ -402,7 +402,28 @@ function errorText(err) {
402
402
  * @param {object} [opts.git] Injectable git-scope (tests).
403
403
  * @param {function} [opts.now] Clock.
404
404
  */
405
- function createQueueWorker({ engine, env = process.env, log = () => {}, git = gitScope, now = Date.now } = {}) {
405
+ function createQueueWorker({
406
+ engine,
407
+ env = process.env,
408
+ log = () => {},
409
+ git = gitScope,
410
+ now = Date.now,
411
+ // May a task fan out AT ALL this session? The default preserves every host's
412
+ // behaviour (a CLI drain, a systemd timer: no entitlement, no clamp), and
413
+ // main.js passes the queue's entitlement gate — see
414
+ // desktop/lib/local/entitlement.js and Phase 4 of
415
+ // docs/upgrade-conversion-plan.md.
416
+ //
417
+ // This exists because the fan-out is otherwise reachable around the desktop's
418
+ // IPC gate: a task queued by the CLI or written into queue.jsonl by hand
419
+ // carries `singlePass: false`, and main.js had already accepted it long
420
+ // before anyone asked about entitlement. Refusing the whole task would be
421
+ // worse than the leak (the free tier keeps working — that is a promise), so
422
+ // the request is honoured as a SINGLE PASS and said out loud via a `note`
423
+ // frame: the run is clamped, never silently different from what the card
424
+ // shows.
425
+ canFanOut = () => true,
426
+ } = {}) {
406
427
  if (!engine || typeof engine.chat !== 'function') {
407
428
  throw new Error('autonomous: a local engine with chat() is required');
408
429
  }
@@ -415,6 +436,18 @@ function createQueueWorker({ engine, env = process.env, log = () => {}, git = gi
415
436
  }
416
437
  };
417
438
 
439
+ /** `canFanOut` is host-supplied, so it is treated like any other injected
440
+ * seam: a predicate that throws must not abort a drain — it fails CLOSED
441
+ * (no fan-out), because the alternative is spending the multiplier the gate
442
+ * exists to withhold. */
443
+ const safeCanFanOut = () => {
444
+ try {
445
+ return canFanOut() !== false;
446
+ } catch {
447
+ return false;
448
+ }
449
+ };
450
+
418
451
  /**
419
452
  * Run ONE queued task. Never throws: a failed task is data (so the drain can
420
453
  * decide whether to continue), not an exception that aborts the queue.
@@ -437,7 +470,21 @@ function createQueueWorker({ engine, env = process.env, log = () => {}, git = gi
437
470
  const model = resolveModel({ model: item.model, env });
438
471
  const ignored = ignoredEnvModel({ model: item.model, env });
439
472
  if (ignored) emit({ type: 'note', taskId: item.id, note: ignored });
440
- const fanout = resolveFanout(item, env);
473
+ const fanoutAsked = resolveFanout(item, env);
474
+ // The clamp. `canFanOut()` is the entitlement answer (true everywhere
475
+ // except a non-Pro desktop session), and it can only ever take a fan-out
476
+ // AWAY: a task the session is not entitled to fan out runs as one plain
477
+ // turn on the same tier, exactly like a `singlePass` task.
478
+ const fanout = fanoutAsked && safeCanFanOut();
479
+ if (fanoutAsked && !fanout) {
480
+ emit({
481
+ type: 'note',
482
+ taskId: item.id,
483
+ note:
484
+ 'fan-out needs Pro (the cloud drain that keeps running after you close ' +
485
+ 'the laptop) — this task ran as a single pass',
486
+ });
487
+ }
441
488
  const effort = resolveEffort({ effort: item.effort, env, fanout });
442
489
  const rounds = maxRounds(env, item.maxRounds);
443
490
  // Approval requests have no one to answer them here; see the header.
@@ -496,6 +543,26 @@ function createQueueWorker({ engine, env = process.env, log = () => {}, git = gi
496
543
  // the default for exactly that reason; the retry/write-up passes in
497
544
  // the engine send `brain: false` for the same one-call reason.
498
545
  autonomous: fanout,
546
+ // THE OPT-OUT HAS TO BE SAID OUT LOUD, or the clamp above is
547
+ // decoration. `autonomous: false` does NOT reach the wire as a
548
+ // "no fan-out": engine.js derives its brain flag as
549
+ // `opts.singlePass ? false : opts.autonomous ? true : undefined`, so
550
+ // false-and-not-singlePass evaluates to `undefined`, and an absent
551
+ // `brain` means "leave the model id's own default alone". The model
552
+ // here IS `nexus-brain`, and aegis1 services/pool_brain.py
553
+ // parse_brain_request enables the fan-out when
554
+ // `is_brain_model(spec)` — no flag or worker count required:
555
+ //
556
+ // enabled = not flag_off and (is_brain_model(spec) or flag_on or ...)
557
+ //
558
+ // So a clamped task went out asking for the full workers+1
559
+ // fan-out and got it, while the log said it ran as a single pass.
560
+ // `brain: false` is the only instruction that outranks the model id
561
+ // (pool_brain's own docstring: "the only way to ask for one provider
562
+ // call on a brain id"), and `singlePass: true` is how a caller
563
+ // produces it. This also fixes the plain `--single-pass` task, which
564
+ // never reached the wire as one for the same reason.
565
+ singlePass: !fanout,
499
566
  effort,
500
567
  workers: fanout ? item.workers || undefined : undefined,
501
568
  // The turn's working directory rides on `env`: engine.js reads the
@@ -1296,6 +1296,16 @@ function createLocalEngine({
1296
1296
  // field is off, which is what keeps every existing caller (the
1297
1297
  // renderer's IPC payloads included) on the cheap read.
1298
1298
  recallDeep: payload && payload.recallDeep === true,
1299
+ // The fan-out opt-out, and it must be an explicit `true` for the same
1300
+ // reason `recallDeep` is. `undefined` is NOT an opt-out: the wire flag
1301
+ // below is `singlePass ? false : autonomous ? true : undefined`, and an
1302
+ // absent `brain` flag lets the *model id* turn the fan-out on by itself
1303
+ // (aegis1 pool_brain.parse_brain_request: `is_brain_model(spec)` alone
1304
+ // enables it). So a clamped task on a brain model fanned out anyway
1305
+ // while the log claimed a single pass — the caller's intent never
1306
+ // reached the wire. Forwarding it here is what lets the refusal be real
1307
+ // rather than a local boolean nobody downstream could see.
1308
+ singlePass: payload && payload.singlePass === true,
1299
1309
  onReasoning,
1300
1310
  idleTimeoutMs: autonomous ? AUTONOMOUS_IDLE_TIMEOUT_MS : undefined,
1301
1311
  // A caller with no live streaming surface (a `--no-stream` CLI flag, a