claude-agent-sdk 1.1.0 → 1.2.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.
- checksums.yaml +4 -4
- data/.yardopts +10 -0
- data/CHANGELOG.md +99 -0
- data/README.md +43 -31
- data/docs/cli-installer.md +26 -4
- data/docs/client.md +29 -11
- data/docs/configuration.md +164 -1
- data/docs/errors.md +32 -2
- data/docs/hooks-and-permissions.md +30 -10
- data/docs/mcp-servers.md +30 -9
- data/docs/observability.md +61 -10
- data/docs/options.md +232 -0
- data/docs/rails.md +263 -18
- data/docs/sessions.md +40 -12
- data/docs/subagents.md +1 -1
- data/docs/types.md +100 -11
- data/lib/claude_agent_sdk/cli_installer.rb +140 -19
- data/lib/claude_agent_sdk/command_builder.rb +83 -108
- data/lib/claude_agent_sdk/fiber_boundary.rb +45 -2
- data/lib/claude_agent_sdk/instrumentation/otel.rb +90 -28
- data/lib/claude_agent_sdk/option_forms.rb +379 -0
- data/lib/claude_agent_sdk/query/run_lifecycle.rb +373 -0
- data/lib/claude_agent_sdk/query.rb +266 -364
- data/lib/claude_agent_sdk/railtie.rb +27 -2
- data/lib/claude_agent_sdk/sdk_mcp_server.rb +78 -26
- data/lib/claude_agent_sdk/session_assembly.rb +311 -0
- data/lib/claude_agent_sdk/session_mutations.rb +112 -92
- data/lib/claude_agent_sdk/session_resume.rb +360 -43
- data/lib/claude_agent_sdk/session_store.rb +31 -2
- data/lib/claude_agent_sdk/sessions.rb +720 -138
- data/lib/claude_agent_sdk/subprocess_cli_transport.rb +228 -29
- data/lib/claude_agent_sdk/testing/session_store_conformance.rb +18 -7
- data/lib/claude_agent_sdk/transcript_mirror_batcher.rb +45 -37
- data/lib/claude_agent_sdk/transport.rb +28 -12
- data/lib/claude_agent_sdk/types/attributes.rb +9 -0
- data/lib/claude_agent_sdk/types/base.rb +85 -15
- data/lib/claude_agent_sdk/types/hooks.rb +73 -0
- data/lib/claude_agent_sdk/types/mcp.rb +37 -1
- data/lib/claude_agent_sdk/types/option_values.rb +186 -4
- data/lib/claude_agent_sdk/types/options.rb +35 -5
- data/lib/claude_agent_sdk/types/permissions.rb +18 -9
- data/lib/claude_agent_sdk/version.rb +1 -1
- data/lib/claude_agent_sdk.rb +223 -418
- data/lib/generators/claude_agent_sdk/install/templates/claude_agent_sdk.rb.tt +6 -0
- data/sig/claude_agent_sdk/types/hooks.rbs +6 -3
- data/sig/claude_agent_sdk/types/option_values.rbs +23 -6
- data/sig/claude_agent_sdk/types/options.rbs +11 -7
- data/sig/claude_agent_sdk/types/permissions.rbs +4 -2
- metadata +9 -4
|
@@ -27,19 +27,25 @@ module ClaudeAgentSDK
|
|
|
27
27
|
|
|
28
28
|
# Rename a session by appending a custom-title entry.
|
|
29
29
|
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
30
|
+
# Repeated calls are safe: the disk listing takes the LAST custom-title
|
|
31
|
+
# in the final 64 KiB of the file (then in the first 64 KiB), the store
|
|
32
|
+
# fold the last one overall. On disk the entry is only seen while it
|
|
33
|
+
# stays inside one of those windows: rename a session that is still
|
|
34
|
+
# running, let 64 KiB of transcript follow, and list_sessions /
|
|
35
|
+
# get_session_info report the previous title again until the CLI resumes
|
|
36
|
+
# the session and re-appends its metadata at the end (the same holds for
|
|
37
|
+
# tag_session). Not fixed here: finding it again means reading the whole
|
|
38
|
+
# file on every listing.
|
|
32
39
|
#
|
|
33
40
|
# @param session_id [String] UUID of the session to rename
|
|
34
41
|
# @param title [String] New session title (whitespace stripped)
|
|
35
42
|
# @param directory [String, nil] Project directory path
|
|
36
|
-
# @raise [ArgumentError] if session_id is invalid or title is
|
|
43
|
+
# @raise [ArgumentError] if session_id is invalid, or title is blank or not a String
|
|
37
44
|
# @raise [Errno::ENOENT] if the session file cannot be found
|
|
38
45
|
def rename_session(session_id:, title:, directory: nil)
|
|
39
46
|
raise ArgumentError, "Invalid session_id: #{session_id}" unless Sessions.valid_session_id?(session_id)
|
|
40
47
|
|
|
41
|
-
stripped = title
|
|
42
|
-
raise ArgumentError, 'title must be non-empty' if stripped.empty?
|
|
48
|
+
stripped = stripped_title(title)
|
|
43
49
|
|
|
44
50
|
data = "#{JSON.generate({ type: 'custom-title', customTitle: stripped, sessionId: session_id })}\n"
|
|
45
51
|
|
|
@@ -54,19 +60,12 @@ module ClaudeAgentSDK
|
|
|
54
60
|
# @param session_id [String] UUID of the session to tag
|
|
55
61
|
# @param tag [String, nil] Tag string, or nil to clear
|
|
56
62
|
# @param directory [String, nil] Project directory path
|
|
57
|
-
# @raise [ArgumentError] if session_id is invalid or tag is empty after sanitization
|
|
63
|
+
# @raise [ArgumentError] if session_id is invalid, or tag is not a String or is empty after sanitization
|
|
58
64
|
# @raise [Errno::ENOENT] if the session file cannot be found
|
|
59
65
|
def tag_session(session_id:, tag:, directory: nil)
|
|
60
66
|
raise ArgumentError, "Invalid session_id: #{session_id}" unless Sessions.valid_session_id?(session_id)
|
|
61
67
|
|
|
62
|
-
|
|
63
|
-
sanitized = sanitize_unicode(tag).strip
|
|
64
|
-
raise ArgumentError, 'tag must be non-empty (use nil to clear)' if sanitized.empty?
|
|
65
|
-
|
|
66
|
-
tag = sanitized
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
data = "#{JSON.generate({ type: 'tag', tag: tag || '', sessionId: session_id })}\n"
|
|
68
|
+
data = "#{JSON.generate({ type: 'tag', tag: sanitized_tag(tag), sessionId: session_id })}\n"
|
|
70
69
|
|
|
71
70
|
append_to_session(session_id, data, directory)
|
|
72
71
|
end
|
|
@@ -164,13 +163,12 @@ module ClaudeAgentSDK
|
|
|
164
163
|
# appended entry carries a fresh uuid + ISO timestamp so adapters that dedupe
|
|
165
164
|
# by entry["uuid"] (per the SessionStore#append contract) treat it correctly.
|
|
166
165
|
#
|
|
167
|
-
# @raise [ArgumentError] if session_id is invalid or title is
|
|
166
|
+
# @raise [ArgumentError] if session_id is invalid, or title is blank or not a String
|
|
168
167
|
# @raise [Errno::ENOENT] if the session is not found in the store
|
|
169
168
|
def rename_session_via_store(session_store:, session_id:, title:, directory: nil)
|
|
170
169
|
raise ArgumentError, "Invalid session_id: #{session_id}" unless Sessions.valid_session_id?(session_id)
|
|
171
170
|
|
|
172
|
-
stripped = title
|
|
173
|
-
raise ArgumentError, 'title must be non-empty' if stripped.empty?
|
|
171
|
+
stripped = stripped_title(title)
|
|
174
172
|
|
|
175
173
|
key = { 'project_key' => Sessions.project_key_for_directory(directory), 'session_id' => session_id }
|
|
176
174
|
ensure_store_session_exists(session_store, key)
|
|
@@ -188,23 +186,17 @@ module ClaudeAgentSDK
|
|
|
188
186
|
# counterpart to tag_session. Pass nil to clear the tag. Tags are
|
|
189
187
|
# Unicode-sanitized before storing.
|
|
190
188
|
#
|
|
191
|
-
# @raise [ArgumentError] if session_id is invalid or tag is empty after sanitization
|
|
189
|
+
# @raise [ArgumentError] if session_id is invalid, or tag is not a String or is empty after sanitization
|
|
192
190
|
# @raise [Errno::ENOENT] if the session is not found in the store
|
|
193
191
|
def tag_session_via_store(session_store:, session_id:, tag:, directory: nil)
|
|
194
192
|
raise ArgumentError, "Invalid session_id: #{session_id}" unless Sessions.valid_session_id?(session_id)
|
|
195
193
|
|
|
196
|
-
|
|
197
|
-
sanitized = sanitize_unicode(tag).strip
|
|
198
|
-
raise ArgumentError, 'tag must be non-empty (use nil to clear)' if sanitized.empty?
|
|
199
|
-
|
|
200
|
-
tag = sanitized
|
|
201
|
-
end
|
|
202
|
-
|
|
194
|
+
tag = sanitized_tag(tag)
|
|
203
195
|
key = { 'project_key' => Sessions.project_key_for_directory(directory), 'session_id' => session_id }
|
|
204
196
|
ensure_store_session_exists(session_store, key)
|
|
205
197
|
session_store.append(key, [{
|
|
206
198
|
'type' => 'tag',
|
|
207
|
-
'tag' => tag
|
|
199
|
+
'tag' => tag,
|
|
208
200
|
'sessionId' => session_id,
|
|
209
201
|
'uuid' => SecureRandom.uuid,
|
|
210
202
|
'timestamp' => iso_now
|
|
@@ -222,10 +214,9 @@ module ClaudeAgentSDK
|
|
|
222
214
|
# @raise [ArgumentError] if session_id is invalid
|
|
223
215
|
def delete_session_via_store(session_store:, session_id:, directory: nil)
|
|
224
216
|
raise ArgumentError, "Invalid session_id: #{session_id}" unless Sessions.valid_session_id?(session_id)
|
|
225
|
-
return unless SessionStore.implements?(session_store, :delete)
|
|
226
217
|
|
|
227
218
|
key = { 'project_key' => Sessions.project_key_for_directory(directory), 'session_id' => session_id }
|
|
228
|
-
session_store.delete(key)
|
|
219
|
+
SessionStores.optional_call(session_store, :delete) { session_store.delete(key) }
|
|
229
220
|
nil
|
|
230
221
|
end
|
|
231
222
|
|
|
@@ -263,6 +254,49 @@ module ClaudeAgentSDK
|
|
|
263
254
|
|
|
264
255
|
# -- Private helpers --
|
|
265
256
|
|
|
257
|
+
# The title to store: stripped and non-empty. A value that is not a usable
|
|
258
|
+
# String (nil, another type, bytes invalid in their encoding) gets the
|
|
259
|
+
# ArgumentError an empty title gets — the boundary check the session ids
|
|
260
|
+
# have — where calling #strip on it raised NoMethodError or an encoding
|
|
261
|
+
# error from inside.
|
|
262
|
+
def stripped_title(title)
|
|
263
|
+
text = utf8_text(title)
|
|
264
|
+
stripped = text ? text.strip : ''
|
|
265
|
+
raise ArgumentError, 'title must be non-empty' if stripped.empty?
|
|
266
|
+
|
|
267
|
+
stripped
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# +value+ as UTF-8 text, or nil when it is not usable text: not a String,
|
|
271
|
+
# or bytes that are not valid text. A binary String (ASCII-8BIT, what
|
|
272
|
+
# File.binread returns) always reports valid_encoding?, so it is read as
|
|
273
|
+
# the UTF-8 it usually holds and checked as such; a String in another
|
|
274
|
+
# encoding is transcoded. Without this, binary bytes that are not UTF-8
|
|
275
|
+
# got past the check and failed later as JSON::GeneratorError or
|
|
276
|
+
# Encoding::CompatibilityError instead of the documented ArgumentError.
|
|
277
|
+
def utf8_text(value)
|
|
278
|
+
return nil unless value.is_a?(String)
|
|
279
|
+
|
|
280
|
+
text = value.encoding == Encoding::BINARY ? value.dup.force_encoding(Encoding::UTF_8) : value.encode(Encoding::UTF_8)
|
|
281
|
+
text.valid_encoding? ? text : nil
|
|
282
|
+
rescue EncodingError
|
|
283
|
+
nil
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# The tag to store: Unicode-sanitized and stripped, or '' (which clears
|
|
287
|
+
# the tag) for nil — only nil: false is not a tag either, and clearing on
|
|
288
|
+
# it would turn a Boolean from untyped input into a destructive write.
|
|
289
|
+
# Same boundary check as stripped_title.
|
|
290
|
+
def sanitized_tag(tag)
|
|
291
|
+
return '' if tag.nil?
|
|
292
|
+
|
|
293
|
+
text = utf8_text(tag)
|
|
294
|
+
sanitized = text ? sanitize_unicode(text).strip : ''
|
|
295
|
+
raise ArgumentError, 'tag must be non-empty (use nil to clear)' if sanitized.empty?
|
|
296
|
+
|
|
297
|
+
sanitized
|
|
298
|
+
end
|
|
299
|
+
|
|
266
300
|
# Raise Errno::ENOENT (as the disk counterparts and fork_session_via_store
|
|
267
301
|
# do) unless the store holds entries for +key+. Without this probe, a
|
|
268
302
|
# rename/tag of a typo'd or stale id APPENDED metadata to a never-written
|
|
@@ -293,8 +327,12 @@ module ClaudeAgentSDK
|
|
|
293
327
|
end
|
|
294
328
|
|
|
295
329
|
def find_in_directory(file_name, directory)
|
|
296
|
-
|
|
297
|
-
|
|
330
|
+
# canonicalize_path, not File.realpath: the transcripts outlive the
|
|
331
|
+
# directory (a removed worktree), and realpath raised Errno::ENOENT for
|
|
332
|
+
# it before the session was even looked for — while the readers, which
|
|
333
|
+
# canonicalize, still found the session through the same directory.
|
|
334
|
+
path = Sessions.canonicalize_path(directory)
|
|
335
|
+
result = try_project_dir(file_name, Sessions.find_project_dir(path), path)
|
|
298
336
|
return result if result
|
|
299
337
|
|
|
300
338
|
worktree_paths = begin
|
|
@@ -305,7 +343,7 @@ module ClaudeAgentSDK
|
|
|
305
343
|
worktree_paths.each do |wt_path|
|
|
306
344
|
next if wt_path == path
|
|
307
345
|
|
|
308
|
-
result = try_project_dir(file_name, Sessions.find_project_dir(wt_path))
|
|
346
|
+
result = try_project_dir(file_name, Sessions.find_project_dir(wt_path), wt_path)
|
|
309
347
|
return result if result
|
|
310
348
|
end
|
|
311
349
|
nil
|
|
@@ -315,11 +353,17 @@ module ClaudeAgentSDK
|
|
|
315
353
|
# in one project dir must not stop the search when the real transcript
|
|
316
354
|
# lives under another (worktree) project dir. Mirrors the read path
|
|
317
355
|
# (Sessions.stat_candidate) and the append path (try_append).
|
|
318
|
-
|
|
356
|
+
# With +path+ (a directory-scoped lookup), the candidate must also be one
|
|
357
|
+
# of that path's own transcripts (Sessions.own_transcript?: a directory
|
|
358
|
+
# the long-path fallback found can hold other paths' sessions).
|
|
359
|
+
def try_project_dir(file_name, project_dir, path = nil)
|
|
319
360
|
return nil unless project_dir
|
|
320
361
|
|
|
321
362
|
candidate = File.join(project_dir, file_name)
|
|
322
|
-
File.size(candidate).positive?
|
|
363
|
+
return nil unless File.size(candidate).positive?
|
|
364
|
+
return nil if path && !Sessions.own_transcript?(project_dir, candidate, path)
|
|
365
|
+
|
|
366
|
+
[candidate, project_dir]
|
|
323
367
|
rescue SystemCallError
|
|
324
368
|
nil
|
|
325
369
|
end
|
|
@@ -433,9 +477,10 @@ module ClaudeAgentSDK
|
|
|
433
477
|
})
|
|
434
478
|
end
|
|
435
479
|
|
|
436
|
-
# Derive title: explicit >
|
|
437
|
-
# prompt, suffixed with " (fork)" when derived. listSessions
|
|
438
|
-
# custom-title from the tail, so this trailer is what
|
|
480
|
+
# Derive title: explicit > the source's listed title (custom, else AI) >
|
|
481
|
+
# its first prompt, suffixed with " (fork)" when derived. listSessions
|
|
482
|
+
# reads the LAST custom-title from the tail, so this trailer is what
|
|
483
|
+
# surfaces.
|
|
439
484
|
fork_title = title&.strip
|
|
440
485
|
fork_title = "#{derive_title.call || 'Forked session'} (fork)" if fork_title.nil? || fork_title.empty?
|
|
441
486
|
|
|
@@ -472,61 +517,30 @@ module ClaudeAgentSDK
|
|
|
472
517
|
[transcript, content_replacements]
|
|
473
518
|
end
|
|
474
519
|
|
|
475
|
-
# Derive a fork title
|
|
476
|
-
#
|
|
477
|
-
#
|
|
478
|
-
#
|
|
479
|
-
#
|
|
480
|
-
#
|
|
481
|
-
#
|
|
520
|
+
# Derive a fork title from already-parsed store entries: the title the
|
|
521
|
+
# store listing shows for the session (custom title, else AI title — the
|
|
522
|
+
# latest occurrence of each, a blank one counting as absent), else its
|
|
523
|
+
# first prompt. Folds the RAW entries (the partitioned transcript has
|
|
524
|
+
# dropped the customTitle/aiTitle metadata — the store half of #837's
|
|
525
|
+
# P0-1 fix) with the fold the listing uses, and takes the title from the
|
|
526
|
+
# folded fields rather than from summary_entry_to_sdk_info: that returns
|
|
527
|
+
# nil for a sidechain or summary-less session, which can still be forked.
|
|
528
|
+
# nil when the session has none of the three (the caller supplies the
|
|
529
|
+
# "Forked session" default).
|
|
482
530
|
def derive_title_from_entries(raw)
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
next unless e.is_a?(Hash)
|
|
487
|
-
|
|
488
|
-
ct = e['customTitle']
|
|
489
|
-
custom = ct if ct.is_a?(String) && !ct.empty?
|
|
490
|
-
at = e['aiTitle']
|
|
491
|
-
ai = at if at.is_a?(String) && !at.empty?
|
|
492
|
-
end
|
|
493
|
-
return custom if custom
|
|
494
|
-
return ai if ai
|
|
495
|
-
|
|
496
|
-
# First-prompt fallback: re-serialize to a JSONL string and reuse the head
|
|
497
|
-
# extractor so skip-patterns/truncation match the disk path exactly.
|
|
498
|
-
# extract_first_prompt_from_head returns '' (truthy in Ruby!) when no
|
|
499
|
-
# prompt qualifies — normalize to nil so the caller's 'Forked session'
|
|
500
|
-
# default actually fires (Python appends `or None` here for this reason).
|
|
501
|
-
jsonl = "#{raw.map { |e| JSON.generate(e) }.join("\n")}\n"
|
|
502
|
-
title = Sessions.extract_first_prompt_from_head(jsonl)
|
|
503
|
-
title.nil? || title.empty? ? nil : title
|
|
531
|
+
data = SessionSummary.fold_session_summary(nil, {}, raw)['data']
|
|
532
|
+
first_prompt = data['first_prompt_locked'] ? data['first_prompt'] : data['command_fallback']
|
|
533
|
+
Sessions.display_title(data['custom_title'], data['ai_title']) || Sessions.presence(first_prompt)
|
|
504
534
|
end
|
|
505
535
|
|
|
506
|
-
# Derive a fork title from the source file
|
|
507
|
-
#
|
|
508
|
-
#
|
|
509
|
-
#
|
|
536
|
+
# Derive a fork title from the source file without slurping it: the title
|
|
537
|
+
# and first prompt the disk listing reports for the session, taken from
|
|
538
|
+
# the same head/tail windows by the same rule. nil when it has neither
|
|
539
|
+
# (build_fork_lines supplies the "Forked session" default).
|
|
510
540
|
def derive_fork_title(file_path, file_size)
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
tail = if file_size > Sessions::LITE_READ_BUF_SIZE
|
|
515
|
-
f.seek(-buf_size, IO::SEEK_END)
|
|
516
|
-
(f.read(buf_size) || '').force_encoding('UTF-8').scrub
|
|
517
|
-
else
|
|
518
|
-
head
|
|
519
|
-
end
|
|
520
|
-
title = Sessions.extract_json_string_field(tail, 'customTitle', last: true) ||
|
|
521
|
-
Sessions.extract_json_string_field(head, 'customTitle', last: true) ||
|
|
522
|
-
Sessions.extract_json_string_field(tail, 'aiTitle', last: true) ||
|
|
523
|
-
Sessions.extract_json_string_field(head, 'aiTitle', last: true) ||
|
|
524
|
-
Sessions.extract_first_prompt_from_head(head)
|
|
525
|
-
# extract_first_prompt_from_head returns '' (truthy in Ruby!) when no
|
|
526
|
-
# prompt qualifies — normalize to nil so the 'Forked session' default
|
|
527
|
-
# fires (Python appends `or None` here for the same reason).
|
|
528
|
-
title.nil? || title.empty? ? nil : title
|
|
529
|
-
end
|
|
541
|
+
head, tail = Sessions.read_head_tail(file_path, file_size)
|
|
542
|
+
title, first_prompt = Sessions.title_and_first_prompt(file_path, head, tail, file_size)
|
|
543
|
+
title || first_prompt
|
|
530
544
|
end
|
|
531
545
|
|
|
532
546
|
# Build a single forked entry with remapped UUIDs.
|
|
@@ -587,11 +601,11 @@ module ClaudeAgentSDK
|
|
|
587
601
|
end
|
|
588
602
|
|
|
589
603
|
def append_to_session_in_directory(session_id, data, file_name, directory)
|
|
590
|
-
path =
|
|
604
|
+
path = Sessions.canonicalize_path(directory) # see find_in_directory
|
|
591
605
|
|
|
592
606
|
# Try the exact/prefix-matched project directory first.
|
|
593
607
|
project_dir = Sessions.find_project_dir(path)
|
|
594
|
-
return if project_dir &&
|
|
608
|
+
return if project_dir && own_append(project_dir, file_name, path, data)
|
|
595
609
|
|
|
596
610
|
# Worktree fallback
|
|
597
611
|
begin
|
|
@@ -604,7 +618,7 @@ module ClaudeAgentSDK
|
|
|
604
618
|
next false if wt_path == path
|
|
605
619
|
|
|
606
620
|
wt_project_dir = Sessions.find_project_dir(wt_path)
|
|
607
|
-
wt_project_dir &&
|
|
621
|
+
wt_project_dir && own_append(wt_project_dir, file_name, wt_path, data)
|
|
608
622
|
end
|
|
609
623
|
return if found
|
|
610
624
|
|
|
@@ -626,6 +640,12 @@ module ClaudeAgentSDK
|
|
|
626
640
|
raise Errno::ENOENT, "Session #{session_id} not found in any project directory"
|
|
627
641
|
end
|
|
628
642
|
|
|
643
|
+
# try_append, for a transcript of +path+'s own (Sessions.own_transcript?).
|
|
644
|
+
def own_append(project_dir, file_name, path, data)
|
|
645
|
+
candidate = File.join(project_dir, file_name)
|
|
646
|
+
Sessions.own_transcript?(project_dir, candidate, path) && try_append(candidate, data)
|
|
647
|
+
end
|
|
648
|
+
|
|
629
649
|
# Try appending to a path.
|
|
630
650
|
#
|
|
631
651
|
# Opens with WRONLY | APPEND (no CREAT) so the open fails with
|
|
@@ -675,11 +695,11 @@ module ClaudeAgentSDK
|
|
|
675
695
|
'Other'
|
|
676
696
|
end
|
|
677
697
|
|
|
678
|
-
private_class_method :find_session_file_with_dir,
|
|
698
|
+
private_class_method :stripped_title, :sanitized_tag, :find_session_file_with_dir,
|
|
679
699
|
:find_in_directory, :try_project_dir, :find_in_all_projects,
|
|
680
700
|
:parse_fork_transcript, :derive_fork_title, :build_forked_entry, :resolve_parent_uuid,
|
|
681
701
|
:append_to_session, :append_to_session_in_directory,
|
|
682
|
-
:append_to_session_global, :try_append, :sanitize_unicode, :unicode_category,
|
|
702
|
+
:append_to_session_global, :own_append, :try_append, :sanitize_unicode, :unicode_category,
|
|
683
703
|
:iso_now, :build_fork_lines, :partition_fork_entries, :derive_title_from_entries,
|
|
684
704
|
:ensure_store_session_exists
|
|
685
705
|
end
|