motifcode 0.2.1 → 0.3.0

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,5 +1,5 @@
1
1
  {
2
- "version": "0.2.1",
3
- "bytes": 440653,
4
- "node": "v24.13.0"
2
+ "version": "0.3.0",
3
+ "bytes": 445102,
4
+ "node": "v22.23.2"
5
5
  }
package/dist/motif.js CHANGED
@@ -831,6 +831,32 @@ function looksLikeLeakedToolCall(result) {
831
831
  if (result.unrecoverable.length > 0) return true;
832
832
  return /<tool_call>|<\/tool_call>/.test(result.content);
833
833
  }
834
+ function stripLoneFence(s) {
835
+ const m = /^```(?:json|tool_call)?\s*([\s\S]*?)\s*```$/.exec(s.trim());
836
+ return m ? (m[1] ?? "").trim() : s.trim();
837
+ }
838
+ function recoverBareToolCall(content, ctx) {
839
+ const body = stripLoneFence(content);
840
+ if (!body.startsWith("{") || !body.endsWith("}")) return null;
841
+ const strict = strictLoad(body);
842
+ const outcome = strict !== null ? { value: coerceArgumentsWrapper(strict), info: CLEAN } : repairBlockDetailed(body, ctx);
843
+ if (outcome.value === null) return null;
844
+ const name = typeof outcome.value["name"] === "string" ? outcome.value["name"] : "";
845
+ if (name === "" || !ctx.specs.has(name)) return null;
846
+ return {
847
+ name,
848
+ arguments: asArguments(outcome.value["arguments"]),
849
+ repaired: true,
850
+ repair: { kind: "detag", lossy: outcome.info.lossy, complete: outcome.info.complete }
851
+ };
852
+ }
853
+ function contentLeaksToolCall(content, ctx) {
854
+ if (/<\/?tool_call>/.test(content)) return true;
855
+ for (const m of content.matchAll(/\{[^{}]*"name"\s*:\s*"([a-z_]+)"[\s\S]*?\}/g)) {
856
+ if (ctx.specs.has(m[1])) return true;
857
+ }
858
+ return false;
859
+ }
834
860
 
835
861
  // packages/protocol/src/scrubber.ts
836
862
  var MARKERS = [THINK_OPEN, THINK_CLOSE];
@@ -969,6 +995,24 @@ var ToolCallChannel = class {
969
995
  ...c.repair ? { repair: c.repair } : {}
970
996
  }
971
997
  );
998
+ if (actions.length === 0) {
999
+ const bare = recoverBareToolCall(r.content, ctx);
1000
+ if (bare) {
1001
+ const action = bare.name === "done" ? {
1002
+ kind: "done",
1003
+ summary: String(bare.arguments["summary"] ?? ""),
1004
+ ...typeof bare.arguments["confirm"] === "boolean" ? { confirm: bare.arguments["confirm"] } : {},
1005
+ ...bare.repair ? { repair: bare.repair } : {}
1006
+ } : {
1007
+ kind: "tool",
1008
+ name: bare.name,
1009
+ arguments: bare.arguments,
1010
+ repaired: true,
1011
+ ...bare.repair ? { repair: bare.repair } : {}
1012
+ };
1013
+ return { actions: [action], content: "", unrecoverable: [], truncated: false };
1014
+ }
1015
+ }
972
1016
  return {
973
1017
  actions,
974
1018
  content: r.content.trim(),
@@ -2931,7 +2975,7 @@ async function runLoop(opts) {
2931
2975
  } : { id: nextId(), name: a.name, arguments: a.arguments }
2932
2976
  );
2933
2977
  if (parsed.actions.length === 0) {
2934
- const leaked = parsed.unrecoverable.length > 0 || parsed.truncated || (parsed.invalidArguments?.length ?? 0) > 0 || channel === "toolcall" && looksLikeLeakedToolCall(parseToolCalls(split.content, ctx));
2978
+ const leaked = parsed.unrecoverable.length > 0 || parsed.truncated || (parsed.invalidArguments?.length ?? 0) > 0 || channel === "toolcall" && (looksLikeLeakedToolCall(parseToolCalls(split.content, ctx)) || contentLeaksToolCall(split.content, ctx));
2935
2979
  if (replyEnds && !leaked && response.finishReason !== "length") {
2936
2980
  session.appendAll(codec.serializeAssistant(split.content, split.reasoning, parsed));
2937
2981
  checkpoint();
@@ -2960,7 +3004,8 @@ async function runLoop(opts) {
2960
3004
  leaked ? "Your last turn did not produce a usable action. It looks like action syntax that failed to parse." : "Your last turn produced no action.",
2961
3005
  channel,
2962
3006
  consecutiveNoAction,
2963
- lastObservation
3007
+ lastObservation,
3008
+ !leaked
2964
3009
  );
2965
3010
  if (consecutiveNoAction === 1) {
2966
3011
  handBack(nudge);
@@ -3160,8 +3205,8 @@ function refusalPrompt(refusals, channel) {
3160
3205
  repairPrompt("", channel).trim()
3161
3206
  ].join("\n");
3162
3207
  }
3163
- function repairPrompt(problem, channel, attempt = 1, lastObservation = "") {
3164
- const how = channel === "toolcall" ? "Emit a well-formed `<tool_call>` block. Watch backslashes: inside JSON strings, shell `$` and regex metacharacters must be escaped or avoided." : channel === "object" ? "Reply with a single well-formed JSON object matching the schema you were given." : "Reply with the XML shape you were given. Command bodies are verbatim \u2014 do not escape anything inside them.";
3208
+ function repairPrompt(problem, channel, attempt = 1, lastObservation = "", cleanReply = false) {
3209
+ const how = cleanReply ? "If the task is already complete, or the message only needs an answer, call `done` now with that answer as the summary. Otherwise take the next concrete step toward the task above \u2014 do not read files or run commands looking for unrelated work to do." : channel === "toolcall" ? "Emit a well-formed `<tool_call>` block. Watch backslashes: inside JSON strings, shell `$` and regex metacharacters must be escaped or avoided." : channel === "object" ? "Reply with a single well-formed JSON object matching the schema you were given." : "Reply with the XML shape you were given. Command bodies are verbatim \u2014 do not escape anything inside them.";
3165
3210
  const parts = [problem, "", how];
3166
3211
  if (attempt >= 2) {
3167
3212
  parts.push(
@@ -6985,22 +7030,23 @@ async function doctor(opts) {
6985
7030
  }
6986
7031
  );
6987
7032
  }
7033
+ const probeBody = JSON.stringify({
7034
+ model: opts.model,
7035
+ temperature: SAMPLING_DEFAULTS.temperature,
7036
+ top_p: SAMPLING_DEFAULTS.top_p,
7037
+ stream: false,
7038
+ max_tokens: 256,
7039
+ messages: [
7040
+ { role: "system", content: "You are a coding agent. Finish by calling the `done` tool." },
7041
+ { role: "user", content: 'Call `done` now with the summary "ok". Do nothing else.' }
7042
+ ],
7043
+ tools: [PROBE_TOOL]
7044
+ });
6988
7045
  try {
6989
7046
  const res = await fetchImpl(`${endpoint}/v1/chat/completions`, {
6990
7047
  method: "POST",
6991
7048
  headers,
6992
- body: JSON.stringify({
6993
- model: opts.model,
6994
- temperature: SAMPLING_DEFAULTS.temperature,
6995
- top_p: SAMPLING_DEFAULTS.top_p,
6996
- stream: false,
6997
- max_tokens: 256,
6998
- messages: [
6999
- { role: "system", content: "You are a coding agent. Finish by calling the `done` tool." },
7000
- { role: "user", content: 'Call `done` now with the summary "ok". Do nothing else.' }
7001
- ],
7002
- tools: [PROBE_TOOL]
7003
- })
7049
+ body: probeBody
7004
7050
  });
7005
7051
  if (res.status === 401 || res.status === 403) {
7006
7052
  const text = (await res.text().catch(() => "")).trim().slice(0, 200);
@@ -7047,12 +7093,31 @@ async function doctor(opts) {
7047
7093
  fix: "the harness splits it client-side; that is a fallback, not the design"
7048
7094
  } : { name: "reasoning parser", state: "unknown", detail: "no reasoning in the probe response" }
7049
7095
  );
7050
- const cached2 = json.usage?.prompt_tokens_details?.cached_tokens;
7096
+ let cached2 = json.usage?.prompt_tokens_details?.cached_tokens;
7097
+ const reports = cached2 !== void 0;
7098
+ try {
7099
+ const again = await fetchImpl(`${endpoint}/v1/chat/completions`, {
7100
+ method: "POST",
7101
+ headers,
7102
+ body: probeBody
7103
+ });
7104
+ if (again.ok) {
7105
+ const json2 = await again.json();
7106
+ const c2 = json2.usage?.prompt_tokens_details?.cached_tokens;
7107
+ if (typeof c2 === "number") cached2 = c2;
7108
+ }
7109
+ } catch {
7110
+ }
7051
7111
  checks.push(
7052
- typeof cached2 === "number" ? {
7112
+ typeof cached2 === "number" && cached2 > 0 ? {
7053
7113
  name: "prefix caching",
7054
7114
  state: "ok",
7055
- detail: `the endpoint reports cached prompt tokens (${cached2} on this probe)`
7115
+ detail: `the endpoint served ${cached2} prompt tokens from its prefix cache on a repeated request`
7116
+ } : reports ? {
7117
+ name: "prefix caching",
7118
+ state: "unknown",
7119
+ detail: "the endpoint reports cached tokens but served none on this probe",
7120
+ fix: "a two-request probe cannot always warm the cache; in a real session the frozen tool order keeps the prefix alive across turns"
7056
7121
  } : {
7057
7122
  name: "prefix caching",
7058
7123
  state: "unknown",
@@ -9144,7 +9209,7 @@ function saveUserSetting(key, value, home = homedir4()) {
9144
9209
  }
9145
9210
 
9146
9211
  // packages/cli/src/main.ts
9147
- var VERSION = "0.2.1";
9212
+ var VERSION = "0.3.0";
9148
9213
  var SHORT_FLAGS = { p: "print", c: "continue", i: "interactive", v: "verbose", h: "help" };
9149
9214
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
9150
9215
  "print",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "motifcode",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Unofficial coding agent harness built for Motif-3: a Claude Code-style terminal session over the hosted endpoint",
6
6
  "keywords": [