flowviant 0.28.0 → 0.28.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.
@@ -60,14 +60,19 @@ export async function reportDeployConfig(repoRoot) {
60
60
  const targets = readDeployConfig(repoRoot);
61
61
  const json = JSON.stringify(targets);
62
62
  if (json === lastConfigJson) return;
63
- // Strip commands/secrets before the server sees the config (metadata only).
63
+ // Scrub command strings before the server sees them a command line can embed
64
+ // an internal host or a synced secret. Only redacted metadata leaves the box.
65
+ const scrubCmds = (o) =>
66
+ o && typeof o === 'object'
67
+ ? Object.fromEntries(Object.entries(o).map(([k, v]) => [k, scrub(String(v ?? ''))]))
68
+ : o;
64
69
  const meta = targets.map((t) => ({
65
70
  id: t.id,
66
71
  label: t.label,
67
72
  provider: t.provider || 'cloudflare',
68
- command: t.command,
69
- build: t.build,
70
- commands: t.commands,
73
+ command: scrub(String(t.command ?? '')),
74
+ build: t.build ? scrub(String(t.build)) : t.build,
75
+ commands: scrubCmds(t.commands),
71
76
  healthcheck: t.healthcheck,
72
77
  healthStatus: t.healthStatus,
73
78
  pushSecrets: t.pushSecrets,
@@ -100,6 +105,11 @@ function run(command, { cwd, env, input }) {
100
105
  /* already gone */
101
106
  }
102
107
  }, 30 * 60_000);
108
+ // A broken pipe (child exits before draining stdin — e.g. a fast-failing
109
+ // `wrangler secret put`) surfaces as an ASYNC 'error' on the stdin stream,
110
+ // which the try/catch below can't catch. Without a listener it's an uncaught
111
+ // exception that kills the whole daemon. Swallow it.
112
+ child.stdin.on('error', () => {});
103
113
  if (input != null) {
104
114
  try {
105
115
  child.stdin.write(input);
@@ -129,7 +139,7 @@ async function verifyHealth(url, status) {
129
139
  for (let i = 0; i < 4; i++) {
130
140
  try {
131
141
  const res = await fetch(url, { signal: AbortSignal.timeout(10_000), redirect: 'manual' });
132
- if (res.status === status) return true;
142
+ if (res.status === Number(status)) return true; // coerce — a string "200" in deploy.json must still match
133
143
  } catch {
134
144
  /* not up yet */
135
145
  }
@@ -148,6 +158,10 @@ const claiming = new Set(); // in-flight guard (single-flight per daemon process
148
158
  export function processDeployJobs(jobs, ctx) {
149
159
  if (!Array.isArray(jobs) || !jobs.length) return;
150
160
  for (const job of jobs) {
161
+ // Defend against a malformed roster element — `job.id` on a null would throw
162
+ // synchronously here (outside the per-job try below) and wedge the whole
163
+ // reconcile loop, since this runs unguarded from the fleet tick.
164
+ if (!job || typeof job.id !== 'string') continue;
151
165
  if (claiming.has(job.id)) continue;
152
166
  claiming.add(job.id);
153
167
  void (async () => {
@@ -185,6 +199,7 @@ export function processDeployJobs(jobs, ctx) {
185
199
 
186
200
  async function runDeploy(job, target, ctx) {
187
201
  const env = { ...process.env, ...deployCreds() }; // inject infra creds; never a file
202
+ delete env.FLEET_TOKEN; // the deploy command has no business reading it; keep it out of a command that might echo its env
188
203
  const logs = [];
189
204
  // Rollback is a single wrangler command; deploy is build → secrets → deploy.
190
205
  if (job.kind === 'rollback') {
package/bin/lib/fleet.mjs CHANGED
@@ -295,6 +295,7 @@ export async function runFleetDaemon() {
295
295
  };
296
296
  const processMergeJobs = (jobs) => {
297
297
  for (const job of jobs ?? []) {
298
+ if (!job || typeof job.id !== 'string') continue; // a null element would wedge the loop
298
299
  if (merging.has(job.id)) continue;
299
300
  merging.add(job.id);
300
301
  (async () => {
@@ -374,6 +375,7 @@ export async function runFleetDaemon() {
374
375
  const cleaning = new Set();
375
376
  const processCleanupJobs = (jobs) => {
376
377
  for (const job of jobs ?? []) {
378
+ if (!job || typeof job.id !== 'string') continue; // a null element would wedge the loop
377
379
  if (cleaning.has(job.id)) continue;
378
380
  cleaning.add(job.id);
379
381
  (async () => {
@@ -889,7 +891,10 @@ export async function runFleetDaemon() {
889
891
  // turn) until we report reground-done; the bare drain flushes anything
890
892
  // whose earlier mint failed.
891
893
  enqueueSweep(roster.codeMapJob);
892
- for (const j of roster.regroundJobs ?? []) enqueueReground(j.intentId, j.prUrl, j.title);
894
+ for (const j of roster.regroundJobs ?? []) {
895
+ if (!j || typeof j.intentId !== 'string') continue; // a null element would throw + wedge the loop
896
+ enqueueReground(j.intentId, j.prUrl, j.title);
897
+ }
893
898
  void drainWiki();
894
899
 
895
900
  // Env sync tick: register/bootstrap/wrap/rotate/sync as the roster block
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowviant",
3
- "version": "0.28.0",
3
+ "version": "0.28.1",
4
4
  "description": "Run your own Claude Code as headless build agents for Flowviant — on your own credentials. Claims dispatched work, opens PRs, captures review evidence, and routes questions back to you.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,7 +30,7 @@
30
30
  "homepage": "https://flowviant.com",
31
31
  "repository": {
32
32
  "type": "git",
33
- "url": "https://github.com/flowviant/cli.git"
33
+ "url": "git+https://github.com/flowviant/cli.git"
34
34
  },
35
35
  "license": "MIT",
36
36
  "bugs": {