@jigging/jig 0.1.0-alpha.22 → 0.1.0-alpha.24

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/README.md CHANGED
@@ -44,7 +44,7 @@ jig new <name>
44
44
  jig review [project] [--generate-contracts] [--allow-resolution-network] [--allow-authority-changes] [--yes] [--details]
45
45
  jig run [flow:path|npm:package|binding:id] [options]
46
46
  jig inspect [flow:path|npm:package|binding:id] [--json]
47
- jig import-contract <source> <destination>
47
+ jig import-contract <descriptor.json|npm:package> <destination>
48
48
  jig completion <bash|zsh|fish>
49
49
  ```
50
50
 
@@ -69,6 +69,17 @@ network socket. See [delegated HTTP access](https://jig.md/guide/http).
69
69
  managed JSON and types before the separate execution-approval question. Plain
70
70
  review does not compile; already-generated contracts need no compiler runtime.
71
71
 
72
+ Declare a project starting point using familiar Run arguments in `jig.ts`:
73
+
74
+ ```ts
75
+ entrypoint: "binding:factory --input @batch.json --attach source=fixtures --out factory-result --timeout 8m",
76
+ ```
77
+
78
+ After review, `jig run` uses that entrypoint. Explicit options override defaults;
79
+ an explicit target bypasses them. Declaration paths are project-relative and job
80
+ files are captured when invoked. Existing output destinations are never replaced.
81
+ A target-only string is valid too. See [project entrypoints](https://jig.md/guide/configuration#project-entrypoint).
82
+
72
83
  `inspect` lists approved targets or shows a target's retained interface without
73
84
  evaluating source, contacting providers, preparing dependencies or changing state.
74
85
  It checks that approval against current local execution identities and reports
@@ -84,7 +84,11 @@ Jig patches the pinned adapter to disable automatic model-based session titles.
84
84
  Requested turns keep their reviewed model and count; deterministic fallback
85
85
  titles remain. The patch also propagates native process failure and clears the
86
86
  shutdown timer after native close. Forced termination cannot report a clean
87
- adapter exit. See `packages/jig/patches/codex-acp@1.8.0.patch` and its companion
87
+ adapter exit. It also registers pending turns before asynchronous prompt setup
88
+ so immediate cancellation waits for and interrupts the correct native turn.
89
+ The adapter waits for the matching native turn-start notification before
90
+ exposing the turn as interruptible; a start reply alone does not establish it.
91
+ See `packages/jig/patches/codex-acp@1.8.0.patch` and its companion
88
92
  note for the correction and removal condition.
89
93
 
90
94
  Copyright 2025 JetBrains s.r.o.
package/dist/index.js CHANGED
@@ -1997,7 +1997,7 @@ function defineJig(input) {
1997
1997
  }
1998
1998
  function normalizeJig(input, canonical) {
1999
1999
  const captured = snapshotJsonObject(input, "Jig definition");
2000
- assertClosedObject(captured, ["flows", "bindings", "grants", "defaultProviders"], "Jig definition");
2000
+ assertClosedObject(captured, ["flows", "bindings", "grants", "defaultProviders", "entrypoint"], "Jig definition");
2001
2001
  const output = {};
2002
2002
  if (Object.hasOwn(captured, "flows")) {
2003
2003
  output.flows = normalizeSource(captured.flows, "flows", canonical);
@@ -2011,6 +2011,11 @@ function normalizeJig(input, canonical) {
2011
2011
  if (Object.hasOwn(captured, "defaultProviders")) {
2012
2012
  output.defaultProviders = normalizeDefaultProviders(captured.defaultProviders);
2013
2013
  }
2014
+ if (Object.hasOwn(captured, "entrypoint")) {
2015
+ if (typeof captured.entrypoint !== "string" || captured.entrypoint.trim().length === 0 || captured.entrypoint.length > 16384)
2016
+ throw new TypeError("entrypoint must be a nonempty string of at most 16384 characters");
2017
+ output.entrypoint = captured.entrypoint;
2018
+ }
2014
2019
  return record(output);
2015
2020
  }
2016
2021
  function normalizeDefaultProviders(value) {
@@ -15,12 +15,14 @@ export interface JigDefinition {
15
15
  readonly bindings?: ProjectSource;
16
16
  readonly grants?: ProjectSource;
17
17
  readonly defaultProviders?: Readonly<Record<string, string>>;
18
+ readonly entrypoint?: string;
18
19
  }
19
20
  export interface JigDefinitionInput {
20
21
  readonly flows?: ProjectSourceInput;
21
22
  readonly bindings?: ProjectSourceInput;
22
23
  readonly grants?: ProjectSourceInput;
23
24
  readonly defaultProviders?: Readonly<Record<string, string>>;
25
+ readonly entrypoint?: string;
24
26
  }
25
27
  export interface FlowRef {
26
28
  readonly kind: 'flow';
@@ -29076,7 +29076,7 @@ var CodexAppServerClient = class {
29076
29076
  if (this.handleStaleTurnNotification(serverNotification, routing)) {
29077
29077
  return;
29078
29078
  }
29079
- this.recordTurnRouting(routing);
29079
+ this.recordTurnRouting(routing, serverNotification.method);
29080
29080
  if (this.handleStaleTurnNotification(serverNotification, routing)) {
29081
29081
  return;
29082
29082
  }
@@ -29155,19 +29155,33 @@ var CodexAppServerClient = class {
29155
29155
  }
29156
29156
  async runTurn(params, onTurnStarted) {
29157
29157
  const capturedCompletions = [];
29158
+ const startedTurns = new Set();
29159
+ let expectedTurnId = null;
29160
+ let resolveReady = () => {};
29161
+ const ready = new Promise((resolve) => { resolveReady = resolve; });
29162
+ const releaseRouting = this.captureTurnRoutings(params.threadId, (turnId, method) => {
29163
+ if (method !== "turn/started") return;
29164
+ startedTurns.add(turnId);
29165
+ if (turnId === expectedTurnId) resolveReady();
29166
+ });
29158
29167
  const releaseCapture = this.captureTurnCompletions(params.threadId, (event) => {
29159
29168
  capturedCompletions.push(event);
29169
+ if (event.turn.id === expectedTurnId) resolveReady();
29160
29170
  });
29161
29171
  try {
29162
29172
  const turnStarted = await this.turnStart(params);
29163
- onTurnStarted?.(turnStarted.turn.id);
29164
- const earlyCompletion = capturedCompletions.find((event) => event.turn.id === turnStarted.turn.id);
29173
+ expectedTurnId = turnStarted.turn.id;
29174
+ // A turn/start reply allocates an ID before the native turn is interruptible.
29175
+ if (!startedTurns.has(expectedTurnId) && !capturedCompletions.some((event) => event.turn.id === expectedTurnId)) {
29176
+ await ready;
29177
+ }
29178
+ const earlyCompletion = capturedCompletions.find((event) => event.turn.id === expectedTurnId);
29179
+ if (earlyCompletion) return earlyCompletion;
29180
+ onTurnStarted?.(expectedTurnId);
29165
29181
  releaseCapture();
29166
- if (earlyCompletion) {
29167
- return earlyCompletion;
29168
- }
29169
- return await this.awaitTurnCompleted(params.threadId, turnStarted.turn.id);
29182
+ return await this.awaitTurnCompleted(params.threadId, expectedTurnId);
29170
29183
  } finally {
29184
+ releaseRouting();
29171
29185
  releaseCapture();
29172
29186
  }
29173
29187
  }
@@ -29601,7 +29615,7 @@ var CodexAppServerClient = class {
29601
29615
  capture();
29602
29616
  }
29603
29617
  }
29604
- recordTurnRouting(routing) {
29618
+ recordTurnRouting(routing, method) {
29605
29619
  if (routing.threadId === null || routing.turnId === null) {
29606
29620
  return;
29607
29621
  }
@@ -29610,7 +29624,7 @@ var CodexAppServerClient = class {
29610
29624
  return;
29611
29625
  }
29612
29626
  for (const capture of captures) {
29613
- capture(routing.turnId);
29627
+ capture(routing.turnId, method);
29614
29628
  }
29615
29629
  }
29616
29630
  handleStaleTurnNotification(notification, routing) {
@@ -33453,6 +33467,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
33453
33467
  }
33454
33468
  return pendingTurnStart;
33455
33469
  };
33470
+ ensurePendingTurnStart();
33456
33471
  const disposePromptRequestCancellation = this.observePromptRequestCancellation(signal, sessionState, activePrompt);
33457
33472
  let eventHandler = null;
33458
33473
  let promptNotificationsActive = true;
@@ -20,7 +20,7 @@ async function execute() {
20
20
  try {
21
21
  if (Number(process.versions.node.split('.')[0]) < 22)
22
22
  throw new Error('Node 22 required');
23
- const { source, channelContracts } = JSON.parse(input);
23
+ const { source } = JSON.parse(input);
24
24
  let types = true;
25
25
  if (source.startsWith('// flow-authoring: ')) {
26
26
  const header = JSON.parse(source.split('\n', 1)[0].slice(19));
@@ -30,7 +30,6 @@ async function execute() {
30
30
  }
31
31
  const result = await compileContract(await stampSource(source, { types }), {
32
32
  signal: stop.signal,
33
- ...(channelContracts === undefined ? {} : { channelContracts }),
34
33
  });
35
34
  process.stdout.write(JSON.stringify({ source: result.source, artifacts: result.artifacts }) + '\n', () => process.exit(0));
36
35
  }
@@ -33,13 +33,12 @@ options, not a function to call silently when opening an unfamiliar package.
33
33
  It returns new source text; the application chooses whether to retain it.
34
34
  `compileContract` requires that pin and refuses a different installed toolchain.
35
35
 
36
- For borrowed agreements, pass their available package-relative names as
37
- `compileContract(source, { channelContracts: ['./contracts/events.json'] })`.
38
- The compiler reads no such files and emits only authored outputs. The caller
39
- must validate and distribute the referenced JSON bytes with the descriptor;
40
- Jig's managed workflow does this and tracks borrowed-input freshness separately
41
- from generated-file ownership. The inventory is bounded to 4096 distinct paths
42
- and 128 KiB; the native contract's smaller closure limits still apply.
36
+ For borrowed agreements, name a package-relative path in the authored channel
37
+ port. The compiler emits that reference without reading or inventorying package
38
+ files. The caller must validate and distribute the referenced JSON bytes with
39
+ the descriptor; Jig's managed workflow resolves only exact references and
40
+ tracks borrowed-input freshness separately from generated-file ownership.
41
+ Native contract closure limits still apply.
43
42
 
44
43
  A minimal authored body is:
45
44
 
@@ -12,5 +12,4 @@ export interface Compilation {
12
12
  export declare function compileContract(source: string, options?: {
13
13
  signal?: AbortSignal;
14
14
  timeoutMs?: number;
15
- channelContracts?: readonly string[];
16
15
  }): Promise<Compilation>;
@@ -1,7 +1,6 @@
1
1
  import { Worker } from 'node:worker_threads';
2
2
  import { AuthoringError, fail } from './errors.js';
3
3
  import { getToolchain, readHeader } from './toolchain.js';
4
- import { channelReference } from './values.js';
5
4
  export { AuthoringError } from './errors.js';
6
5
  export { getToolchain, stampSource } from './toolchain.js';
7
6
  /** Compile explicit source into a bounded artifact packet, with no publication. */
@@ -15,18 +14,11 @@ export async function compileContract(source, options = {}) {
15
14
  if (options.signal?.aborted)
16
15
  fail('CANCELLED', 'Compilation was cancelled before it started.');
17
16
  const header = readHeader(source, await getToolchain());
18
- const channelContracts = options.channelContracts ?? [];
19
- if (!Array.isArray(channelContracts) ||
20
- channelContracts.length > 4096 ||
21
- channelContracts.some((path) => !channelReference(path)) ||
22
- Buffer.byteLength(JSON.stringify(channelContracts)) > 131072 ||
23
- new Set(channelContracts).size !== channelContracts.length)
24
- fail('SOURCE_INVALID', 'Supply at most 4096 distinct package-local channel contract paths within 128 KiB.');
25
17
  if (options.signal?.aborted)
26
18
  fail('CANCELLED', 'Compilation was cancelled before it started.');
27
19
  return new Promise((resolve, reject) => {
28
20
  const worker = new Worker(new URL('./worker.js', import.meta.url), {
29
- workerData: { source, types: header.types, channelContracts: [...channelContracts] },
21
+ workerData: { source, types: header.types },
30
22
  env: {},
31
23
  execArgv: [],
32
24
  stdout: true,
@@ -48,7 +48,7 @@ interface ChannelDescriptor {
48
48
  }
49
49
  export declare const schemaId = "https://flow.jig.md/schemas/schema-0.json";
50
50
  export declare const contractId = "https://flow.jig.md/schemas/invocation-contract-0.schema.json";
51
- export declare function lower(program: Program, channelContracts?: ReadonlySet<string>): {
51
+ export declare function lower(program: Program): {
52
52
  descriptor: Descriptor;
53
53
  projections: Record<string, {
54
54
  $schema: string;
@@ -25,7 +25,7 @@ function number(value, target) {
25
25
  }
26
26
  return n === 0 ? 0 : n;
27
27
  }
28
- export function lower(program, channelContracts = new Set()) {
28
+ export function lower(program) {
29
29
  const entries = [...program.stateMap(invocationKey)];
30
30
  if (entries.length !== 1)
31
31
  fail('SOURCE_INVALID', 'Declare exactly one @invocation on a namespace.');
@@ -258,10 +258,6 @@ export function lower(program, channelContracts = new Set()) {
258
258
  $defs: reachableDefinitions(emit(type), definitions),
259
259
  };
260
260
  }
261
- for (const port of Object.values(ports)) {
262
- if (!Object.hasOwn(channels, port.contract.slice(2)) && !channelContracts.has(port.contract))
263
- fail('SOURCE_INVALID', 'Every port must reference a generated channel or a supplied offline contract.', owner, port.contract);
264
- }
265
261
  const projections = Object.create(null);
266
262
  for (const [type, path] of program.stateMap(projectionKey)) {
267
263
  if (!authored(type) ||
@@ -4,8 +4,8 @@ import { compileProgram } from './host.js';
4
4
  import { lower } from './lower.js';
5
5
  import { checkText, encode } from './values.js';
6
6
  try {
7
- const { source, types, channelContracts } = workerData;
8
- const result = lower(await compileProgram(source), new Set(channelContracts));
7
+ const { source, types } = workerData;
8
+ const result = lower(await compileProgram(source));
9
9
  const artifacts = Object.create(null);
10
10
  artifacts['FLOW.contract.json'] = encode(result.descriptor);
11
11
  for (const [path, schema] of Object.entries(result.projections))
@@ -96,6 +96,12 @@
96
96
  "$ref": "#/$defs/defaultSelector"
97
97
  },
98
98
  "maxProperties": 256
99
+ },
100
+ "entrypoint": {
101
+ "type": "string",
102
+ "minLength": 1,
103
+ "maxLength": 16384,
104
+ "description": "An exact Run target followed by optional Jig run invocation arguments. Jig validates quoting, permitted options, project-relative paths and target membership. No shell execution or expansion."
99
105
  }
100
106
  },
101
107
  "additionalProperties": false
@@ -167,8 +173,16 @@
167
173
  }
168
174
  },
169
175
  "required": ["kind", "client"],
170
- "if": { "required": ["retainSessions"] },
171
- "then": { "properties": { "client": { "const": "codex" } } },
176
+ "if": {
177
+ "required": ["retainSessions"]
178
+ },
179
+ "then": {
180
+ "properties": {
181
+ "client": {
182
+ "const": "codex"
183
+ }
184
+ }
185
+ },
172
186
  "additionalProperties": false
173
187
  },
174
188
  {
@@ -1997,7 +1997,7 @@ function defineJig(input) {
1997
1997
  }
1998
1998
  function normalizeJig(input, canonical) {
1999
1999
  const captured = snapshotJsonObject(input, "Jig definition");
2000
- assertClosedObject(captured, ["flows", "bindings", "grants", "defaultProviders"], "Jig definition");
2000
+ assertClosedObject(captured, ["flows", "bindings", "grants", "defaultProviders", "entrypoint"], "Jig definition");
2001
2001
  const output = {};
2002
2002
  if (Object.hasOwn(captured, "flows")) {
2003
2003
  output.flows = normalizeSource(captured.flows, "flows", canonical);
@@ -2011,6 +2011,11 @@ function normalizeJig(input, canonical) {
2011
2011
  if (Object.hasOwn(captured, "defaultProviders")) {
2012
2012
  output.defaultProviders = normalizeDefaultProviders(captured.defaultProviders);
2013
2013
  }
2014
+ if (Object.hasOwn(captured, "entrypoint")) {
2015
+ if (typeof captured.entrypoint !== "string" || captured.entrypoint.trim().length === 0 || captured.entrypoint.length > 16384)
2016
+ throw new TypeError("entrypoint must be a nonempty string of at most 16384 characters");
2017
+ output.entrypoint = captured.entrypoint;
2018
+ }
2014
2019
  return record(output);
2015
2020
  }
2016
2021
  function normalizeDefaultProviders(value) {