@playcraft/cli 0.1.19 → 0.1.21

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.
@@ -1,11 +1,19 @@
1
1
  const LOCAL_ATTR = /<(?:script|img|link|source|video|audio|iframe)\b[^>]*?\b(?:src|href)\s*=\s*["']([^"']+)["']/gi;
2
- const CSS_URL = /url\(\s*["']?([^"')]+)["']?\s*\)/gi;
3
- /** A reference the sealed document would load from the local build, not from the network or inline data. */
2
+ /** A CSS `url(`, not the tail of a name such as `createObjectURL(` or `getAudioURL(`. */
3
+ const CSS_URL = /(?<![\w$.-])url\(\s*["']?([^"')]+)["']?\s*\)/gi;
4
+ /** An inline script's body, kept apart from its tag so `<script src>` is still checked. */
5
+ const SCRIPT_BODY = /(<script\b[^>]*>)[\s\S]*?(<\/script\s*>)/gi;
6
+ /**
7
+ * A reference the sealed document would load from the local build, not from the network or inline
8
+ * data. Inline script code is not read: inlined libraries call functions named `…URL(` and build
9
+ * `"url(" + src + ")"` strings at run time, and neither is a file the document loads.
10
+ */
4
11
  export function findExternalLocalReferences(html) {
12
+ const markup = html.replace(SCRIPT_BODY, '$1$2');
5
13
  const found = new Set();
6
14
  for (const expression of [LOCAL_ATTR, CSS_URL]) {
7
15
  expression.lastIndex = 0;
8
- for (const match of html.matchAll(expression)) {
16
+ for (const match of markup.matchAll(expression)) {
9
17
  const value = (match[1] ?? '').trim();
10
18
  if (isLocalReference(value))
11
19
  found.add(value);
@@ -246,11 +246,15 @@ async function executeTurn(state, scopeKey, operation, payload, recovery) {
246
246
  const reserved = state.journal.getOperation(scopeKey) ?? operation;
247
247
  if (result.outcome === 'uncertain' || result.outcome === 'failed' || !result.turnId) {
248
248
  const failed = result.outcome === 'failed';
249
+ // Codex no longer knows the thread although the binding said it could continue. Marking the
250
+ // binding lost lets the same turn, sent again with the conversation, rebind the session.
251
+ const threadLost = failed && result.error === 'unknown_thread';
249
252
  await state.journal.putSession({
250
253
  ...(state.journal.getSession(sessionId) ?? binding),
251
254
  turnStatus: failed ? undefined : 'starting',
252
255
  turnBlock: failed ? undefined : 'uncertain',
253
256
  activeTurnId: undefined,
257
+ ...(threadLost ? { recoverability: 'history_only' } : {}),
254
258
  });
255
259
  return persist(state, scopeKey, transitionOperation(reserved, failed ? 'failed' : 'uncertain', {
256
260
  error: result.outcome === 'failed' ? result.error : 'native_receipt_lost',
@@ -1,7 +1,7 @@
1
1
  import { readObservedFile, withObservedFile } from '../workspaces/revisions.js';
2
2
  import { registerWorkspace, revisionScope, workspaceRoot, WorkspaceContextError } from '../workspaces/registry.js';
3
3
  import { randomUUID } from 'node:crypto';
4
- import { workspaceRegistrationRequestSchema, HTTP_STATUS_BY_ERROR, assertLeaseFence, buildHandshakeDocument, capability, findUntrustedRuntimeFields, gitFileRefSchema, hashOperationPayload, sourceAckSchema, operationAcceptRequestSchema, operationKindSchema, operationScopeKey, resolveIdempotentAccept, nativeHistoryReadSchema, runtimeError, runtimeConnectivityDocumentSchema, unavailableRuntimeCapabilities, } from '@playcraft/common/cloud-workspace/runtime';
4
+ import { workspaceRegistrationRequestSchema, HTTP_STATUS_BY_ERROR, assertLeaseFence, buildHandshakeDocument, capability, findUntrustedRuntimeFields, gitFileRefSchema, hashOperationPayload, isLostThreadError, sourceAckSchema, operationAcceptRequestSchema, operationKindSchema, operationScopeKey, resolveIdempotentAccept, nativeHistoryReadSchema, runtimeError, runtimeConnectivityDocumentSchema, unavailableRuntimeCapabilities, } from '@playcraft/common/cloud-workspace/runtime';
5
5
  import { controlTokenMatches, extractControlToken } from './auth.js';
6
6
  import { applyRuntimeEndpointPatch } from './endpoints.js';
7
7
  import { canExecuteKind, executeOperation } from './dispatch.js';
@@ -383,7 +383,12 @@ async function acceptOperation(state, request, response) {
383
383
  }));
384
384
  return;
385
385
  }
386
- if (decision.action === 'reuse') {
386
+ // A turn refused for a lost thread never reached Codex. Sent again with the conversation, it
387
+ // runs afresh under the same request, so the message is shown and answered only once.
388
+ const retryWithRecovery = decision.action === 'reuse' && parsed.data.kind === 'session.turn'
389
+ && Boolean(parsed.data.sessionRecovery) && decision.operation.status === 'failed'
390
+ && isLostThreadError(decision.operation.error);
391
+ if (decision.action === 'reuse' && !retryWithRecovery) {
387
392
  if (decision.operation.status === 'uncertain') {
388
393
  writeError(response, runtimeError('uncertain_operation', decision.operation.error ?? 'native_receipt_lost', {
389
394
  operationId: decision.operation.operationId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcraft/cli",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
@@ -48,8 +48,8 @@
48
48
  "@gltf-transform/core": "^4.3.0",
49
49
  "@gltf-transform/extensions": "^4.3.0",
50
50
  "@gltf-transform/functions": "^4.3.0",
51
- "@playcraft/build": "^0.1.19",
52
- "@playcraft/common": "^0.1.19",
51
+ "@playcraft/build": "^0.1.21",
52
+ "@playcraft/common": "^0.1.21",
53
53
  "better-sqlite3-pinned": "npm:better-sqlite3@13.0.3",
54
54
  "chokidar": "^4.0.3",
55
55
  "commander": "^13.1.0",