@volter/supercode-teams 0.3.121 → 0.3.123

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.
package/bin/teams.mjs CHANGED
@@ -187,7 +187,12 @@ async function main() {
187
187
  process.on('SIGTERM', stop);
188
188
  return;
189
189
  }
190
- if (sub === 'status') { out(readJson(p.machine, { running: false })); return; }
190
+ if (sub === 'status') {
191
+ // and every supercode process here still running a release an install replaced (t_8e109edc)
192
+ let stale_releases = null;
193
+ try { stale_releases = await (await import('@volter/supercode-health/drift')).staleSupercode(); } catch { /* not read */ }
194
+ out({ ...readJson(p.machine, { running: false }), stale_releases }); return;
195
+ }
191
196
  // `machine restart [--machine M] [--reason text]`: the daemon (this one, or M's through the server) restarts under its service.
192
197
  if (sub === 'restart') return withNode(async (rpc) => out(await rpc.call('machine.restart', { reason: flags.reason ? String(flags.reason) : null })));
193
198
  die('machine start|status|restart');
package/bin/workspace.mjs CHANGED
@@ -2,9 +2,9 @@
2
2
  import { StringDecoder } from 'node:string_decoder';
3
3
  import { hostname } from 'node:os';
4
4
  import { agentSessionHint } from './workspace/agent-session.mjs';
5
- import { fileURLToPath } from 'node:url';
5
+ import { fileURLToPath, pathToFileURL } from 'node:url';
6
6
  import { existsSync, readFileSync, realpathSync, rmSync } from 'node:fs';
7
- import { basename, join, resolve } from 'node:path';
7
+ import { basename, dirname, join, resolve } from 'node:path';
8
8
  import { execFileSync, spawn } from 'node:child_process';
9
9
  import { createHash } from 'node:crypto';
10
10
  import { SupercodeHarnessClient } from '@volter/supercode-harness-sdk';
@@ -1119,6 +1119,7 @@ async function connectorCommand(verb, { flags, rest, output, client, context, te
1119
1119
  if (!flags.json) {
1120
1120
  process.stdout.write(`Connected ${saved.machine.name} to team ${context.team_name ?? context.team_id}; it stays connected (a ${service.kind ?? 'background'} service).\n`);
1121
1121
  for (const line of service.message_tools ?? []) process.stdout.write(` ${line}\n`);
1122
+ for (const line of await releaseSkills()) process.stdout.write(` ${line}\n`);
1122
1123
  return;
1123
1124
  }
1124
1125
  return output({ version: 1, context: scope(context), data: { machine: saved.machine, source: saved.source, service, ...(addedRule ? { rule: addedRule } : {}) } });
@@ -1486,3 +1487,17 @@ function stampStderr() {
1486
1487
  };
1487
1488
  process.stderr.__stamped = true;
1488
1489
  }
1490
+
1491
+ /**
1492
+ * The skills of the release this Teams package belongs to (`@volter/supercode`'s lib/skills.mjs, the package that
1493
+ * holds it), put into every harness here: a machine connected to a team has agents that know supercode
1494
+ * (t_266912ff). A Teams package run from a checkout or installed alone has no release beside it, and installs none.
1495
+ */
1496
+ async function releaseSkills() {
1497
+ const release = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', '..');
1498
+ try {
1499
+ if (JSON.parse(readFileSync(join(release, 'package.json'), 'utf8')).name !== '@volter/supercode') return [];
1500
+ const { installSkills } = await import(pathToFileURL(join(release, 'lib', 'skills.mjs')).href);
1501
+ return installSkills();
1502
+ } catch { return []; }
1503
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-teams",
3
- "version": "0.3.121",
3
+ "version": "0.3.123",
4
4
  "type": "module",
5
5
  "description": "A machine daemon serving this machine's doors, and a local or remote Teams server for shared session discovery, OpenTelemetry ingestion and access to enrolled machines.",
6
6
  "license": "MIT",
package/worker/index.mjs CHANGED
@@ -143,15 +143,42 @@ export class TeamsServer extends DurableObject {
143
143
  }
144
144
  }
145
145
 
146
+ /** What the Durable Object's runtime said of a failed call (its error's own flags), for the log and the answer. */
147
+ const objectFailure = (error) => ({
148
+ name: String(error?.name ?? 'Error'),
149
+ message: String(error?.message ?? error).slice(0, 200),
150
+ retryable: error?.retryable === true,
151
+ overloaded: error?.overloaded === true,
152
+ remote: error?.remote === true,
153
+ reset: error?.durableObjectReset === true,
154
+ });
155
+
146
156
  export default {
147
157
  async fetch(request, env) {
148
- try { return await env.TEAMS_SERVER.get(env.TEAMS_SERVER.idFromName('server')).fetch(request); }
158
+ const repeatable = request.method === 'GET' || request.method === 'HEAD';
159
+ const call = () => env.TEAMS_SERVER.get(env.TEAMS_SERVER.idFromName('server')).fetch(repeatable ? request.clone() : request);
160
+ // A request safe to repeat (a read, or a link's WebSocket upgrade: every machine's connection) is tried once more
161
+ // when the runtime says the failure is retryable and the object is not overloaded: a reset object (its code
162
+ // updated, or evicted and restarted) answers the second call. Fleet-wide 503s for minutes with no deploy behind
163
+ // them (2026-10-06, 14:04Z and 18:41-18:44Z) dropped every machine's link; what the runtime said of them was only
164
+ // in the log, which now says it in one JSON line and the answer names it.
165
+ let first = null;
166
+ try { return await call(); }
149
167
  catch (error) {
150
- // Failures before the HTTP handler (object initialization, migrations or
151
- // storage) otherwise become a platform HTML page, not our API contract.
152
- const requestId = crypto.randomUUID();
153
- console.error(`Teams request ${requestId} failed before response: ${error.message}`);
154
- return Response.json({ version: 1, error: { code: 'unavailable', message: 'Teams server is temporarily unavailable', request_id: requestId, retryable: true } }, { status: 503, headers: { 'Retry-After': '5', 'X-Request-ID': requestId } });
168
+ first = objectFailure(error);
169
+ if (!(repeatable && first.retryable && !first.overloaded)) return unavailable(first, null);
155
170
  }
171
+ try { return await call(); }
172
+ catch (error) { return unavailable(objectFailure(error), first); }
156
173
  },
157
174
  };
175
+
176
+ /** Failures before the HTTP handler (object initialization, migrations, storage, a reset) as our API contract, not a
177
+ * platform HTML page. The answer goes to callers not yet authenticated, so it names only the error's kind and the
178
+ * runtime's flags; its message (storage or SQL text, internal paths) is in the log line its request id leads to. */
179
+ function unavailable(failure, retried) {
180
+ const requestId = crypto.randomUUID();
181
+ console.error(JSON.stringify({ event: 'teams_unavailable', request_id: requestId, failure, ...(retried ? { first: retried } : {}) }));
182
+ const cause = `${failure.name}${failure.reset ? ', object reset' : ''}${failure.overloaded ? ', overloaded' : ''}${retried ? ', after one retry' : ''}`;
183
+ return Response.json({ version: 1, error: { code: 'unavailable', message: `Teams server is temporarily unavailable (${cause})`, request_id: requestId, retryable: true, cause: { name: failure.name, retryable: failure.retryable, overloaded: failure.overloaded, remote: failure.remote, reset: failure.reset } } }, { status: 503, headers: { 'Retry-After': failure.overloaded ? '15' : '5', 'X-Request-ID': requestId } });
184
+ }